├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
└── workflows
│ ├── build_kitkat.yml
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .metadata
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ └── values
│ │ │ └── string.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── org
│ │ │ │ └── ca
│ │ │ │ └── squawker
│ │ │ │ └── SquawkerApplication.java
│ │ ├── kotlin
│ │ │ └── org
│ │ │ │ └── ca
│ │ │ │ └── squawker
│ │ │ │ └── MainActivity.kt
│ │ ├── play_store_512.png
│ │ └── res
│ │ │ ├── drawable-night
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ ├── ic_launcher_monochrome.xml
│ │ │ ├── ic_notification.xml
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── raw
│ │ │ └── keep.xml
│ │ │ └── values
│ │ │ ├── string.xml
│ │ │ └── styles.xml
│ │ └── profile
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ └── values
│ │ └── string.xml
├── build.gradle
├── fastlane
│ ├── Appfile
│ ├── Fastfile
│ └── README.md
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── settings_aar.gradle
├── assets
└── icon.png
├── build-play.sh
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── changelogs
│ ├── 1.txt
│ ├── 10.txt
│ ├── 11.txt
│ ├── 12.txt
│ ├── 13.txt
│ ├── 14.txt
│ ├── 15.txt
│ ├── 16.txt
│ ├── 17.txt
│ ├── 18.txt
│ ├── 19.txt
│ ├── 2.txt
│ ├── 20.txt
│ ├── 21.txt
│ ├── 22.txt
│ ├── 23.txt
│ ├── 24.txt
│ ├── 25.txt
│ ├── 26.txt
│ ├── 27.txt
│ ├── 28.txt
│ ├── 29.txt
│ ├── 3.txt
│ ├── 30.txt
│ ├── 31.txt
│ ├── 32.txt
│ ├── 33.txt
│ ├── 34.txt
│ ├── 35.txt
│ ├── 36.txt
│ ├── 37.txt
│ ├── 38.txt
│ ├── 39.txt
│ ├── 4.txt
│ ├── 40.txt
│ ├── 41.txt
│ ├── 42.txt
│ ├── 43.txt
│ ├── 44.txt
│ ├── 45.txt
│ ├── 46.txt
│ ├── 47.txt
│ ├── 5.txt
│ ├── 6.txt
│ ├── 7.txt
│ ├── 8.txt
│ ├── 9.txt
│ ├── default.txt
│ └── next.txt
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ └── 3.jpg
│ ├── short_description.txt
│ └── title.txt
├── fritter
├── android
│ └── app
│ │ └── src
│ │ └── main
│ │ └── res
│ │ ├── values-ar
│ │ └── strings.xml
│ │ ├── values-b+be+Latn
│ │ └── strings.xml
│ │ ├── values-be
│ │ └── strings.xml
│ │ ├── values-ca
│ │ └── strings.xml
│ │ ├── values-cs
│ │ └── strings.xml
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-eo
│ │ └── strings.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-et
│ │ └── strings.xml
│ │ ├── values-eu
│ │ └── strings.xml
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values-hi
│ │ └── strings.xml
│ │ ├── values-id
│ │ └── strings.xml
│ │ ├── values-it
│ │ └── strings.xml
│ │ ├── values-ja
│ │ └── strings.xml
│ │ ├── values-ko
│ │ └── strings.xml
│ │ ├── values-ml
│ │ └── strings.xml
│ │ ├── values-nb-rNO
│ │ └── strings.xml
│ │ ├── values-nl
│ │ └── strings.xml
│ │ ├── values-or
│ │ └── strings.xml
│ │ ├── values-pl
│ │ └── strings.xml
│ │ ├── values-pt-rBR
│ │ └── strings.xml
│ │ ├── values-pt
│ │ └── strings.xml
│ │ ├── values-ro
│ │ └── strings.xml
│ │ ├── values-ru
│ │ └── strings.xml
│ │ ├── values-tr
│ │ └── strings.xml
│ │ ├── values-uk
│ │ └── strings.xml
│ │ └── values-zh-rCN
│ │ └── strings.xml
└── ios
│ └── Runner
│ └── Base.lproj
│ ├── ar
│ └── Main.storyboard
│ ├── be-Latn
│ └── Main.storyboard
│ ├── be
│ └── Main.storyboard
│ ├── ca
│ └── Main.storyboard
│ ├── cs
│ └── Main.storyboard
│ ├── de
│ └── Main.storyboard
│ ├── eo
│ └── Main.storyboard
│ ├── es
│ └── Main.storyboard
│ ├── et
│ └── Main.storyboard
│ ├── eu
│ └── Main.storyboard
│ ├── fr
│ └── Main.storyboard
│ ├── hi
│ └── Main.storyboard
│ ├── id
│ └── Main.storyboard
│ ├── it
│ └── Main.storyboard
│ ├── ja
│ └── Main.storyboard
│ ├── ko
│ └── Main.storyboard
│ ├── ml
│ └── Main.storyboard
│ ├── nb-NO
│ └── Main.storyboard
│ ├── nl
│ └── Main.storyboard
│ ├── or
│ └── Main.storyboard
│ ├── pl
│ └── Main.storyboard
│ ├── pt-BR
│ └── Main.storyboard
│ ├── pt
│ └── Main.storyboard
│ ├── ro
│ └── Main.storyboard
│ ├── ru
│ └── Main.storyboard
│ ├── tr
│ └── Main.storyboard
│ ├── uk
│ └── Main.storyboard
│ └── zh-Hans
│ └── Main.storyboard
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── 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
│ │ │ ├── AppIcon-20@2x.png
│ │ │ ├── AppIcon-20@2x~ipad.png
│ │ │ ├── AppIcon-20@3x.png
│ │ │ ├── AppIcon-20~ipad.png
│ │ │ ├── AppIcon-29.png
│ │ │ ├── AppIcon-29@2x.png
│ │ │ ├── AppIcon-29@2x~ipad.png
│ │ │ ├── AppIcon-29@3x.png
│ │ │ ├── AppIcon-29~ipad.png
│ │ │ ├── AppIcon-40@2x.png
│ │ │ ├── AppIcon-40@2x~ipad.png
│ │ │ ├── AppIcon-40@3x.png
│ │ │ ├── AppIcon-40~ipad.png
│ │ │ ├── AppIcon-60@2x~car.png
│ │ │ ├── AppIcon-60@3x~car.png
│ │ │ ├── AppIcon-83.5@2x~ipad.png
│ │ │ ├── AppIcon@2x.png
│ │ │ ├── AppIcon@2x~ipad.png
│ │ │ ├── AppIcon@3x.png
│ │ │ ├── AppIcon~ios-marketing.png
│ │ │ ├── AppIcon~ipad.png
│ │ │ └── Contents.json
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ ├── Main.storyboard
│ │ ├── ar
│ │ │ └── LaunchScreen.storyboard
│ │ ├── be-Latn
│ │ │ └── LaunchScreen.storyboard
│ │ ├── be
│ │ │ └── LaunchScreen.storyboard
│ │ ├── ca
│ │ │ └── LaunchScreen.storyboard
│ │ ├── cs
│ │ │ └── LaunchScreen.storyboard
│ │ ├── de
│ │ │ └── LaunchScreen.storyboard
│ │ ├── eo
│ │ │ └── LaunchScreen.storyboard
│ │ ├── es
│ │ │ └── LaunchScreen.storyboard
│ │ ├── et
│ │ │ └── LaunchScreen.storyboard
│ │ ├── eu
│ │ │ └── LaunchScreen.storyboard
│ │ ├── fr
│ │ │ └── LaunchScreen.storyboard
│ │ ├── hi
│ │ │ └── LaunchScreen.storyboard
│ │ ├── id
│ │ │ └── LaunchScreen.storyboard
│ │ ├── it
│ │ │ └── LaunchScreen.storyboard
│ │ ├── ja
│ │ │ └── LaunchScreen.storyboard
│ │ ├── ko
│ │ │ └── LaunchScreen.storyboard
│ │ ├── ml
│ │ │ └── LaunchScreen.storyboard
│ │ ├── nb-NO
│ │ │ └── LaunchScreen.storyboard
│ │ ├── nl
│ │ │ └── LaunchScreen.storyboard
│ │ ├── or
│ │ │ └── LaunchScreen.storyboard
│ │ ├── pl
│ │ │ └── LaunchScreen.storyboard
│ │ ├── pt-BR
│ │ │ └── LaunchScreen.storyboard
│ │ ├── pt
│ │ │ └── LaunchScreen.storyboard
│ │ ├── ro
│ │ │ └── LaunchScreen.storyboard
│ │ ├── ru
│ │ │ └── LaunchScreen.storyboard
│ │ ├── tr
│ │ │ └── LaunchScreen.storyboard
│ │ ├── uk
│ │ │ └── LaunchScreen.storyboard
│ │ └── zh-Hans
│ │ │ └── LaunchScreen.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
└── build
│ └── XCBuildData
│ ├── 45c68c5a5ddc4a08fc8a532b246d1332-desc.xcbuild
│ ├── 45c68c5a5ddc4a08fc8a532b246d1332-manifest.xcbuild
│ ├── BuildDescriptionCacheIndex-356b71b1a528c44b947f9456849ec4c1
│ ├── build.db
│ ├── f0d31a7c171441a95488a75e7a2d9228-desc.xcbuild
│ └── f0d31a7c171441a95488a75e7a2d9228-manifest.xcbuild
├── lib
├── client
│ ├── app_http_client.dart
│ ├── client.dart
│ ├── client_account.dart
│ ├── client_guest_account.dart
│ ├── client_regular_account.dart
│ └── client_unauthenticated.dart
├── constants.dart
├── database
│ ├── entities.dart
│ └── repository.dart
├── generated
│ ├── intl
│ │ ├── messages_all.dart
│ │ ├── messages_ar.dart
│ │ ├── messages_be.dart
│ │ ├── messages_be_Latn.dart
│ │ ├── messages_ca.dart
│ │ ├── messages_cs.dart
│ │ ├── messages_de.dart
│ │ ├── messages_en.dart
│ │ ├── messages_eo.dart
│ │ ├── messages_es.dart
│ │ ├── messages_et.dart
│ │ ├── messages_eu.dart
│ │ ├── messages_fr.dart
│ │ ├── messages_hi.dart
│ │ ├── messages_hu.dart
│ │ ├── messages_ia.dart
│ │ ├── messages_id.dart
│ │ ├── messages_it.dart
│ │ ├── messages_ja.dart
│ │ ├── messages_ko.dart
│ │ ├── messages_ml.dart
│ │ ├── messages_nb_NO.dart
│ │ ├── messages_nl.dart
│ │ ├── messages_or.dart
│ │ ├── messages_pl.dart
│ │ ├── messages_pt.dart
│ │ ├── messages_pt_BR.dart
│ │ ├── messages_ro.dart
│ │ ├── messages_ru.dart
│ │ ├── messages_tr.dart
│ │ ├── messages_uk.dart
│ │ ├── messages_vi.dart
│ │ ├── messages_zh_Hans.dart
│ │ └── messages_zh_Hant.dart
│ └── l10n.dart
├── group
│ ├── _feed.dart
│ ├── _settings.dart
│ ├── group_model.dart
│ └── group_screen.dart
├── home
│ ├── _feed.dart
│ ├── _groups.dart
│ ├── _missing.dart
│ ├── _saved.dart
│ ├── home_model.dart
│ └── home_screen.dart
├── import_data_model.dart
├── l10n
│ ├── intl_ar.arb
│ ├── intl_be.arb
│ ├── intl_be_Latn.arb
│ ├── intl_ca.arb
│ ├── intl_cs.arb
│ ├── intl_de.arb
│ ├── intl_en.arb
│ ├── intl_eo.arb
│ ├── intl_es.arb
│ ├── intl_et.arb
│ ├── intl_eu.arb
│ ├── intl_fr.arb
│ ├── intl_hi.arb
│ ├── intl_hu.arb
│ ├── intl_ia.arb
│ ├── intl_id.arb
│ ├── intl_it.arb
│ ├── intl_ja.arb
│ ├── intl_ko.arb
│ ├── intl_ml.arb
│ ├── intl_nb_NO.arb
│ ├── intl_nl.arb
│ ├── intl_or.arb
│ ├── intl_pl.arb
│ ├── intl_pt.arb
│ ├── intl_pt_BR.arb
│ ├── intl_ro.arb
│ ├── intl_ru.arb
│ ├── intl_tr.arb
│ ├── intl_uk.arb
│ ├── intl_vi.arb
│ ├── intl_zh_Hans.arb
│ └── intl_zh_Hant.arb
├── loading.dart
├── main.dart
├── models.dart
├── profile
│ ├── _follows.dart
│ ├── _saved.dart
│ ├── _tweets.dart
│ ├── profile.dart
│ └── profile_model.dart
├── saved
│ └── saved_tweet_model.dart
├── search
│ ├── search.dart
│ └── search_model.dart
├── settings
│ ├── _about.dart
│ ├── _account.dart
│ ├── _data.dart
│ ├── _general.dart
│ ├── _home.dart
│ ├── _theme.dart
│ ├── settings.dart
│ └── settings_export_screen.dart
├── status.dart
├── subscriptions
│ ├── _groups.dart
│ ├── _import.dart
│ ├── _list.dart
│ ├── subscriptions.dart
│ └── users_model.dart
├── trends
│ ├── _list.dart
│ ├── _settings.dart
│ ├── _tabs.dart
│ ├── trends.dart
│ └── trends_model.dart
├── tweet
│ ├── _card.dart
│ ├── _context_menu.dart
│ ├── _entities.dart
│ ├── _media.dart
│ ├── _photo.dart
│ ├── _video.dart
│ ├── _video_controls.dart
│ ├── conversation.dart
│ ├── tweet.dart
│ └── tweet_exceptions.dart
├── ui
│ ├── dates.dart
│ ├── errors.dart
│ └── physics.dart
├── user.dart
└── utils
│ ├── accent_util.dart
│ ├── cache.dart
│ ├── crypto_util.dart
│ ├── data_service.dart
│ ├── debounce.dart
│ ├── downloads.dart
│ ├── iterables.dart
│ ├── misc.dart
│ ├── notifiers.dart
│ ├── route_util.dart
│ ├── share_util.dart
│ ├── translation.dart
│ ├── ui_util.dart
│ └── urls.dart
├── pubspec.lock
├── pubspec.yaml
├── release.sh
└── test
├── thread_test.dart
├── tweets-carmack-replies.json
└── tweets-carmack.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*.dart]
5 | max_line_length = 120
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea for Squawker
3 | labels: [enhancement]
4 | body:
5 | - type: textarea
6 | id: describe-problem
7 | attributes:
8 | label: Describe the problem
9 | description: |
10 | Describe the problem that needs solving
11 |
12 | A clear description of what the problem is. E.g. I'm always frustrated when [...]
13 |
14 | - type: textarea
15 | id: describe-solution
16 | attributes:
17 | label: Describe the solution
18 | description: |
19 | Describe the solution you'd like
20 |
21 | A clear description of what you want to happen.
22 | validations:
23 | required: true
24 |
--------------------------------------------------------------------------------
/.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 | /profile_files/
34 |
35 | # Web related
36 | lib/generated_plugin_registrant.dart
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
--------------------------------------------------------------------------------
/.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: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.lineLength": 120,
3 | "editor.rulers": [
4 | 120
5 | ],
6 | "[dart]": {
7 | "editor.rulers": [
8 | 120
9 | ],
10 | "editor.formatOnSave": true,
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2024 j-fbriere
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/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/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Squawker (Debug)
4 |
--------------------------------------------------------------------------------
/android/app/src/main/java/org/ca/squawker/SquawkerApplication.java:
--------------------------------------------------------------------------------
1 | package org.ca.squawker;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.content.pm.ResolveInfo;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import androidx.multidex.MultiDex;
10 |
11 | import io.flutter.app.FlutterApplication;
12 |
13 | import java.util.List;
14 |
15 | public class SquawkerApplication extends FlutterApplication {
16 | @Override
17 | protected void attachBaseContext(Context base) {
18 | super.attachBaseContext(base);
19 | MultiDex.install(this);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/android/app/src/main/play_store_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/play_store_512.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_notification.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/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 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/raw/keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Squawker
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/profile/res/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Squawker (Profile)
4 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/android/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | package_name("org.ca.squawker")
2 |
--------------------------------------------------------------------------------
/android/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | # This file contains the fastlane.tools configuration
2 | # You can find the documentation at https://docs.fastlane.tools
3 | #
4 | # For a list of all available actions, check out
5 | #
6 | # https://docs.fastlane.tools/actions
7 | #
8 | # For a list of all available plugins, check out
9 | #
10 | # https://docs.fastlane.tools/plugins/available-plugins
11 | #
12 |
13 | # Uncomment the line if you want fastlane to automatically update itself
14 | # update_fastlane
15 |
16 | default_platform(:android)
17 |
18 | platform :android do
19 | desc "Runs all the tests"
20 | lane :test do
21 | gradle(task: "test")
22 | end
23 |
24 | desc "Submit a new Beta Build to Crashlytics Beta"
25 | lane :beta do
26 | gradle(task: "clean assembleRelease")
27 | crashlytics
28 |
29 | # sh "your_script.sh"
30 | # You can also use other beta testing services here
31 | end
32 |
33 | desc "Deploy a new version to the Google Play"
34 | lane :deploy do
35 | gradle(task: "clean assembleRelease")
36 | upload_to_play_store
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/android/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ================
3 | # Installation
4 |
5 | Make sure you have the latest version of the Xcode command line tools installed:
6 |
7 | ```
8 | xcode-select --install
9 | ```
10 |
11 | Install _fastlane_ using
12 | ```
13 | [sudo] gem install fastlane -NV
14 | ```
15 | or alternatively using `brew install fastlane`
16 |
17 | # Available Actions
18 | ## Android
19 | ### android test
20 | ```
21 | fastlane android test
22 | ```
23 | Runs all the tests
24 | ### android beta
25 | ```
26 | fastlane android beta
27 | ```
28 | Submit a new Beta Build to Crashlytics Beta
29 | ### android deploy
30 | ```
31 | fastlane android deploy
32 | ```
33 | Deploy a new version to the Google Play
34 |
35 | ----
36 |
37 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
38 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
39 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
40 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 | android.enableR8.fullMode=true
6 | extra-gen-snapshot-options=--obfuscate
7 | android.bundle.enableUncompressedNativeLibs=true
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 |
20 | plugins {
21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
22 | id "com.android.application" version '7.3.0' apply false
23 | id "org.jetbrains.kotlin.android" version "1.8.21" apply false
24 | }
25 |
26 | include ":app"
27 |
--------------------------------------------------------------------------------
/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/assets/icon.png
--------------------------------------------------------------------------------
/build-play.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | fvm flutter packages pub run intl_utils:generate
6 | fvm flutter build appbundle --dart-define=app.flavor=play --release --no-tree-shake-icons
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/1.txt:
--------------------------------------------------------------------------------
1 | * Changed the naming from Quacker to Squawker.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/10.txt:
--------------------------------------------------------------------------------
1 | * Enhancement change from #8 Makes quoted status cards optional to #16 make link previews in tweets optional
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/11.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #18 Translation broken
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/12.txt:
--------------------------------------------------------------------------------
1 | * Fix translate to target chosen squawker language (instead of current platform)
2 | * Fix translate to treat response as UTF-8
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/13.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #19: Keep read position in feed after refreshing.
2 | The feature is not activated by default. Must be activated in Settings / Keep feeds offset.
3 | * Fix the black screen when the app is in recent screen mode.
4 | * Change the feed cache flush from scroll up to tap on the rounded arrow icon.
5 | * Change the feed cache flush to be specific to the current feed, not all feeds (the main feed and the groups feed).
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/14.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #25 Downloaded images not showing in gallery.
2 | * Implement feature #24 The app opens on x.com.
3 | * Implement feature #22 Add supported activities to the context menu of selected text.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/15.txt:
--------------------------------------------------------------------------------
1 | * Fix issue of feature #19 where position is not kept after the fetch of feeds (at startup or after refreshes).
2 | * Fix issue #35 Refresh button in the top bar doesn't work.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/16.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #38 Unable to create group because [Ok] is outside dialog due to longer german button labels.
2 | * Change the startup or refresh initial scroll animation to scroll jump.
3 | * Implement feature #40 Option in settings to increase text size in the app.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/17.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #37 Wide video doesn't play after going fullscreen.
2 | * Fix issue of exception raised when video thumbnail image URL is null.
3 | * Fix feature #43 to allow to go back to home page after launching the app from a tweet or profile link.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/18.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #45 The feed is empty. The fix implements the creation of temporary Twitter/X guest accounts to access the Twitter/X API.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/19.txt:
--------------------------------------------------------------------------------
1 | * Fix for the graphql requests (profile request for instance).
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/2.txt:
--------------------------------------------------------------------------------
1 | * Fixed the search user with changes on Twitter of July 2023.
2 | * Fixed the subscription timeline with changes on Twitter of July 2023.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/20.txt:
--------------------------------------------------------------------------------
1 | * Temporary Twitter/X guest account is now valid 20 days instead of 10.
2 | * Implement feature #48 Option to have best available quality of downloaded videos (thanks @votemp).
3 | * Fix some tweets that when tapping for detail are giving an error.
4 | * Implement the push notification permission request for Android >= 13.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/21.txt:
--------------------------------------------------------------------------------
1 | * Implement some safeguards to avoid the Twitter/X 24 hours embargo when rate limits are reached.
2 | * Implement an automatic Twitter/X guest account creation (one new per IP address per day) if the embargo is in place.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/22.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #63 Clicking on tweets sometimes shows sponsored content/Ads.
2 | * Fix greyed checkboxes
3 | * Fix issue from discussion #66 of launching Squawker from a shorten Twitter/X link (https://t.co/...)
4 | and also when a Twitter/X tweet link has additional parts (eg. ends with /photo/1).
5 | * Implement feature #61 Show a tweet count for feed/groups when scrolling forward and the keep offset option is enabled.
6 | * Implement feature #68 Add an option to have a confirmation when the application is closing.
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/23.txt:
--------------------------------------------------------------------------------
1 | * Fix #81 Nitter instances in AndroidManifest.xml are out of date.
2 | * Implement enhancement #90 M3 toggle (thanks to @TheHCJ).
3 |
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/24.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #75 Add "hide from feed" option in profiles.
2 | The option is available when tapping on the subscribe/unsubscribe user icon.
3 | * Fix: after import the blue checks disappear for the subscriptions list page.
4 | * Fix: after subscribe/unsubscribe the feed page do not reload.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/25.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #100 Add an android share access so Squawker can be run from Firefox.
2 | * Fix the case when Twitter/X returns empty responses with a status code of 200.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/26.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #105 Home screen isn't populated right after importing settings and subscriptions.
2 | * Corrections due to Flutter upgrade.
3 | * Adjust the twitter.com URLs to x.com.
4 | * Fix issue with feature #100 Add an android share access so Squawker can be run from Firefox.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/27.txt:
--------------------------------------------------------------------------------
1 | * Change the icons for imports in relation to issue #107.
2 | * Fix issue #127 Floods are not shown. To get the floods conversation, replies must be included.
3 | * Add an option to specify the default subscription tab.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/28.txt:
--------------------------------------------------------------------------------
1 | * Fix: Get activities for Android version 11+ (API version 30+)
2 | * Feature: Settings UI improvement (thanks to @mrhcjones)
3 | * Feature: Allow enhanced Twitter/X requests for feeds as an option in 'Settings/General/X API' subsection,
4 | although those requests have lower rate limits (50 per 15 min vs 190 per 15 min).
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/29.txt:
--------------------------------------------------------------------------------
1 | * Feature: Allow enhanced Twitter/X requests for searches as an option in 'Settings/General/X API' subsection,
2 | although those requests have lower rate limits (50 per 15 min vs 190 per 15 min).
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/3.txt:
--------------------------------------------------------------------------------
1 | * Added Squawker to IzzyOnDroid (thanks to @TheHCJ).
2 | * Changed the application icon (thanks to @TheHCJ).
3 | * Added a concurrency control mechanism to get the guest token.
4 | * Fixed the feed with changes on Twitter of July 2023.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/30.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #158 Bug with the dialog to modify the tweets font size.
2 | * Fix issue #159 The error message is not shown for feed or correct for searches/trend.
3 | * Feature that adds the Twitter/X guest accounts list to import/export from settings / data.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/31.txt:
--------------------------------------------------------------------------------
1 | * New strategy to create guest accounts: instead of being based of the date-time of last one created (24+ hours),
2 | now it's based on the date-time of the last attempt to create one (24+ hours) and also the device "public" IP.
3 | * Fix issue #165 Profile page do not show any tweet.
4 | The "old" v1.1 Twitter/X API requests do not work anymore for the profile. New types of Twitter/X API requests has been implemented,
5 | which are called "enhanced" profile requests as an option in 'Settings/General/X API' subsection.
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/32.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #171 Cannot open embedded tweet or retweet.
2 | * Feature #167 Squawker now have its own weblate project (https://hosted.weblate.org/engage/squawker/).
3 | Everyone that want to help translate Squawker is welcome :-)
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/33.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #114 Chinese (Simplified) translated text is not displayed when selected.
2 | * Guest accounts are not preemptively deleted after 25 days anymore. We'll have to wait until they expired by Twitter/X...
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/34.txt:
--------------------------------------------------------------------------------
1 | * Feature #185 Using regular Twitter/X accounts.
2 | To add the information of an existing regular Twitter/X account, go to Settings / Account, tap on the plus icon.
3 | Only the username and password are mandatory. When saved an authentication token is created. It is automatically renewed every 30 days.
4 | There can be many existing regular Twitter/X accounts.
5 | To delete a regular Twitter/X account information (and its authenticated token), swipe to the left and tap on delete.
6 | The project's wiki describe how to create a (regular) Twitter/X account.
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/35.txt:
--------------------------------------------------------------------------------
1 | * Implement issue #193 Cannot add 2FA Protected account (or feature #195 Two factor authentication support).
2 | * Implement issue #192 Allows different strategy to use the accounts.
3 | There are 3 possible choices that can be set in the Account page:
4 | - prioritize regular account (choice by default)
5 | - use both guest and regular account
6 | - use only regular account.
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/36.txt:
--------------------------------------------------------------------------------
1 | * Fix issues #177 and #196 Import only working partially.
2 | * Fix issue #198 Suspicious activity ask confirmation code when adding account.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/37.txt:
--------------------------------------------------------------------------------
1 | * Add a message at startup for users that have not yet setup a regular account.
2 | * Improve the management of the last active guest accounts to check for expiration.
3 | * Improve the requests for feed/groups allowing more subscriptions to be used. Also add a circular progress indicator during the loading.
4 | * Implement feature #205 Ability to modify the regular account information.
5 | * Fix #194 v3.7.0 freeze.
6 | * Additional verifications when importing tokens.
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/38.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #208 Squawker not saving account credentials when it is closed
2 | and also issue #216 After updating to latest version, twitter / X account didn't save from the previous version. It was wiped out.
3 | * Fix issue #200 Download media feature not working.
4 | * Implement feature #111 Allow audio & video to continue playing when app is minimised.
5 | Also implement feature #210 Muted videos: start video only, keep music playing.
6 | There are now two options implemented, enabled by default (under Settings / General / Media) :
7 | - Allows video to continue to play when Squawker is in the background
8 | - Allows video/music of other apps to play in the background while a video is playing in Squawker.
9 | * Implement feature #219 Allow to set a proxy HTTP(S) or SOCKS5.
10 | The proxy can be set under Settings / General then tap on the Proxy item.
11 | The format is: scheme://[user:pwd@]host:port.
12 | Eg https://1.2.3.4:5678 or socks5://who:ami@9.8.7.6:2468.
13 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/39.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #225 Unable to save the Media. Twitter/X returned a status of 405 bug.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/4.txt:
--------------------------------------------------------------------------------
1 | * Improve the feed tweets to have the quoted status cards.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/40.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #226 App does not open, showing a black screen. Some db records have unexpected null fields.
2 | * Implement feature #218 Share tweet as image.
3 | * Implement feature #232 Allow unauthenticated access like with a browser.
4 | What is possible to do with unauthenticated access:
5 | - view specific tweets (but not their threads);
6 | - view profiles;
7 | - view what is trending;
8 | - import subscriptions.
9 | What is not possible to do with unauthenticated access:
10 | - view search results;
11 | - view feed/groups timelines;
12 | - view tweet threads.
13 | * Implement feature #203 View profile picture and banner.
14 | * Implement feature #234 Ability to import specific subscriptions.
15 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/41.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #231 Display Top tweets instead of latest when searching hashtag.
2 | * Fix issue #236 Replies show up in feed, when the setting is off.
3 | * Fix issue #245 UNIQUE constraint failed: twitter_token error at the account renewal when Squawker opens.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/42.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #145 Option to use system accent color.
2 | Go to Settings / Theme then select Accent in Theme list.
3 | * Implement feature #174 Add an ability to block/filter out accounts.
4 | Go to Settings / General / Feed, select Exclusions in feed then add usernames.
5 | * Implement feature #240 Retrieve more tweets on search.
6 | By keeping scrolling there are new requests automatically made.
7 | * Implement feature #242 Rearrange subscriptions\pin favorite subscriptions to top.
8 | On the Subscriptions page, simply drag each subscription to the desired position.
9 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/43.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #129 Let tweet cards be True Dark as well (thanks @TheHCJ)
2 | * Implement interface upgrade (thanks @TheHCJ)
3 | - added: followers and following are now tappable on profiles
4 | - added: switches and appbar to feed settings
5 | - added: material 3 navigationBar
6 | - added: tweets bigger icons and bookmark button
7 | - added: material symbols
8 | - changed: locations picker bottom sheet
9 | - changed: import subscriptions bottom sheet
10 | - removed: followers and following tabs on profile page
11 | - removed: material 2 support
12 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/44.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #284 Hebrew Translation.
2 | * Fix unnecessary rebuilds when using ModalRoute.of() with flutter 3.19.x.
3 | * Fix issue #245 UNIQUE constraint failed: twitter_token error at the account renewal when Squawker opens.
4 | * Use local time zone for absolute time (PR #285) (thanks @zihu12).
5 | * Fix issue #293 Profile search keeps searching for first search term entered, even after deleting it and entering new search term.
6 | * Fix issue #263 Search result not displaying properly.
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/45.txt:
--------------------------------------------------------------------------------
1 | * Implement PR #297 Add true black tweet cards when system theme is dark (thanks @TheHJC).
2 | * Implement feature #296 Show Community Note.
3 | * Fix issue #258 Rearranging subscriptions duplicates subscription list.
4 | * Implement feature #291 Switch back navigation bar material theme on true black.
5 | * Implement an option to show or hide the labels on the navigation bar icons.
6 | Enabled by default, the option is located in Settings / General / Navigation bar labels.
7 | * Implement feature #279 Allow creation of a new group in the "add to group" screen.
8 | Simply tap on the plus icon at the top right of the dialog box.
9 | * Implement feature #272 Option to disable animations.
10 | The animations are enabled by default, the option to disable it is located in Settings / General / Navigation animations.
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/46.txt:
--------------------------------------------------------------------------------
1 | * Fix the feature #129 Let tweet cards be True Dark as well (thanks @TheHJC).
2 | * Fix issue #309 Toolbar icons are not aligned.
3 | * PR #311 Add search button to profile view to find tweets from current user (thanks @alotbsol555).
4 | * PR #319 Allow to specify custom LibreTranslate instances (thanks @zihu12).
5 | The list can be reordered, instances can be added or removed from the list.
6 | An API key can be set for each instance.
7 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/47.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #316 Add fixupx.com as supported link.
2 | * Implement PR #323 Put newly saved tweets to the top of the saved view.
3 | * Fix issue #324 Customized translator settings not applied on Application Restart.
4 | * Fix issue #326 Translation error of Indonesian.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/5.txt:
--------------------------------------------------------------------------------
1 | * Fix the duplicates from the feed tweets.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/6.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #9 Retweets on Feed display the wrong username.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/7.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #10 RT tweets appear to be broken.
2 | * Remove the twitter URLs from the tweets in feeds.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/8.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #13 Touching the RT account names gives an error.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/9.txt:
--------------------------------------------------------------------------------
1 | * Fix issue #14 Can't open/check replies of media tweets in Feed.
2 | * Enhancement #8 Makes quoted status cards optional
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/default.txt:
--------------------------------------------------------------------------------
1 | * Implement feature #316 Add fixupx.com as supported link.
2 | * Implement PR #323 Put newly saved tweets to the top of the saved view.
3 | * Fix issue #324 Customized translator settings not applied on Application Restart.
4 | * Fix issue #326 Translation error of Indonesian.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/next.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/fastlane/metadata/android/en-US/changelogs/next.txt
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | An open-source privacy oriented Twitter/X client
2 |
3 | * Privacy: No tracking, with all data local
4 | * No ads: Not clogged by multiple ads
5 | * Feed: View all your subscriptions in a chronological feed
6 | * Subscriptions: Follow and group accounts
7 | * Search: Find users and tweets
8 | * Bookmarks: Save tweets locally and offline
9 | * Trends: See what's trending in the world
10 | * Polls: View results without needing to vote
11 | * Light and Dark themes: Protect your eyes
12 | * And more!
13 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | An open-source privacy oriented Twitter/X client
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Squawker
2 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ar/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-b+be+Latn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-be/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ca/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-cs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-eo/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-et/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-eu/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-hi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-id/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ml/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-nb-rNO/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-or/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-pl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-pt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ro/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-uk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/android/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/ar/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/be-Latn/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/be/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/ca/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/cs/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/de/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/eo/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/et/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/eu/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/fr/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/hi/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/id/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/it/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/ja/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/ko/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/ml/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/nb-NO/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/nl/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/or/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/pl/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/pt-BR/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/pt/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/ru/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/tr/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/uk/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/fritter/ios/Runner/Base.lproj/zh-Hans/Main.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/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 | $(DEVELOPMENT_LANGUAGE)
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? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/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.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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/AppIcon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.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/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/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/Base.lproj/ar/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/be-Latn/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/be/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/ca/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/cs/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/de/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/eo/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/et/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/eu/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/fr/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/hi/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/id/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/it/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/ja/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/ko/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/ml/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/nb-NO/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/nl/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/or/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/pl/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/pt-BR/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/pt/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/ru/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/tr/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/uk/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/zh-Hans/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 | "";
2 |
--------------------------------------------------------------------------------
/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 | Squawker
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 | NSAppTransportSecurity
45 |
46 | NSAllowsArbitraryLoads
47 |
48 |
49 | LSApplicationQueriesSchemes
50 |
51 | https
52 | http
53 |
54 | io.flutter.embedded_views_preview
55 |
56 | NSCameraUsageDescription
57 | This app needs camera access to scan QR codes
58 |
59 |
60 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/build/XCBuildData/45c68c5a5ddc4a08fc8a532b246d1332-desc.xcbuild:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/build/XCBuildData/45c68c5a5ddc4a08fc8a532b246d1332-desc.xcbuild
--------------------------------------------------------------------------------
/ios/build/XCBuildData/BuildDescriptionCacheIndex-356b71b1a528c44b947f9456849ec4c1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/build/XCBuildData/BuildDescriptionCacheIndex-356b71b1a528c44b947f9456849ec4c1
--------------------------------------------------------------------------------
/ios/build/XCBuildData/build.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/build/XCBuildData/build.db
--------------------------------------------------------------------------------
/ios/build/XCBuildData/f0d31a7c171441a95488a75e7a2d9228-desc.xcbuild:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-fbriere/squawker/2c099b2560837bb95c2eb9444aa4b9bc9a7b25ba/ios/build/XCBuildData/f0d31a7c171441a95488a75e7a2d9228-desc.xcbuild
--------------------------------------------------------------------------------
/lib/client/app_http_client.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 | import 'package:http/http.dart' as http;
4 | import 'package:http/io_client.dart';
5 | import 'package:socks5_proxy/socks_client.dart';
6 |
7 | class AppHttpClient {
8 |
9 | static IOClient? _ioClient;
10 |
11 | static void setProxy(String? proxy) {
12 | if (proxy?.isEmpty ?? true) {
13 | _ioClient = null;
14 | return;
15 | }
16 | Uri uri = Uri.parse(proxy!);
17 | HttpClient httpClient;
18 | if (uri.scheme == 'socks5') {
19 | httpClient = HttpClient();
20 | String? username;
21 | String? password;
22 | if (uri.userInfo.isNotEmpty) {
23 | List userPwdLst = uri.userInfo.split(':');
24 | username = userPwdLst[0];
25 | if (userPwdLst.length > 1) {
26 | password = userPwdLst[1];
27 | }
28 | }
29 | SocksTCPClient.assignToHttpClient(httpClient, [
30 | ProxySettings(InternetAddress(uri.host), uri.port, username: username, password: password),
31 | ]);
32 | }
33 | else if (uri.scheme.isEmpty || uri.scheme == 'http' || uri.scheme == 'https') {
34 | httpClient = HttpClient();
35 | httpClient.findProxy = (Uri url) {
36 | String foundProxy = HttpClient.findProxyFromEnvironment(url, environment: {"https_proxy": proxy});
37 | return foundProxy;
38 | };
39 | }
40 | else {
41 | throw Exception('Uri scheme ${uri.scheme} not implemented.');
42 | }
43 | httpClient.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
44 | _ioClient = IOClient(httpClient);
45 | }
46 |
47 | static Future httpGet(Uri url, {Map? headers}) async {
48 | if (_ioClient != null) {
49 | return _ioClient!.get(url, headers: headers);
50 | }
51 | else {
52 | return http.get(url, headers: headers);
53 | }
54 | }
55 |
56 | static Future httpPost(Uri url, {Map? headers, Object? body, Encoding? encoding}) async {
57 | if (_ioClient != null) {
58 | return _ioClient!.post(url, headers: headers, body: body, encoding: encoding);
59 | }
60 | else {
61 | return http.post(url, headers: headers, body: body, encoding: encoding);
62 | }
63 | }
64 |
65 | static Future httpSend(http.Request request) async {
66 | if (_ioClient != null) {
67 | return _ioClient!.send(request);
68 | }
69 | else {
70 | http.Client baseClient = http.Client();
71 | return baseClient.send(request);
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/lib/client/client_unauthenticated.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:http/http.dart' as http;
4 | import 'package:logging/logging.dart';
5 | import 'package:squawker/client/app_http_client.dart';
6 |
7 | const String unauthenticatedAccessToken = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
8 |
9 | class TwitterUnauthenticated {
10 | static final log = Logger('TwitterUnauthenticated');
11 |
12 | static String? _guestToken;
13 |
14 | static Future getGuestToken() async {
15 | if (_guestToken != null) {
16 | return _guestToken!;
17 | }
18 | log.info('Posting https://api.twitter.com/1.1/guest/activate.json');
19 | var response = await AppHttpClient.httpPost(Uri.parse('https://api.twitter.com/1.1/guest/activate.json'),
20 | headers: {
21 | 'Authorization': 'Bearer $unauthenticatedAccessToken'
22 | }
23 | );
24 |
25 | if (response.statusCode == 200 && response.body.isNotEmpty) {
26 | var result = jsonDecode(response.body);
27 | if (result.containsKey('guest_token')) {
28 | _guestToken = result['guest_token'];
29 | return _guestToken!;
30 | }
31 | }
32 | throw TwitterUnauthenticatedException('Unable to get the guest token. The response (${response.statusCode}) from Twitter/X was: ${response.body}');
33 | }
34 |
35 | static Future fetch(Uri uri, {Map? headers}) async {
36 | String guestToken = await getGuestToken();
37 | var response = await AppHttpClient.httpGet(uri, headers: {
38 | ...?headers,
39 | 'Authorization': 'Bearer $unauthenticatedAccessToken',
40 | 'Content-Type': 'application/json',
41 | 'X-Twitter-Active-User': 'yes',
42 | 'Authority': 'api.twitter.com',
43 | 'Origin': 'https://twitter.com',
44 | 'Referer': 'https://twitter.com/',
45 | 'Pragma': 'no-cache',
46 | 'cache-control': 'no-cache',
47 | 'Accept-Encoding': 'gzip, deflate',
48 | 'Accept-Language': 'en-US,en;q=0.9',
49 | 'Accept': '*/*',
50 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
51 | 'X-Guest-Token': guestToken,
52 | 'cookie': 'gt=$guestToken'
53 | });
54 |
55 | return response;
56 | }
57 | }
58 |
59 | class TwitterUnauthenticatedException implements Exception {
60 | final String message;
61 |
62 | TwitterUnauthenticatedException(this.message);
63 |
64 | @override
65 | String toString() {
66 | return message;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_hi.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a hi locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
12 |
13 | import 'package:intl/intl.dart';
14 | import 'package:intl/message_lookup_by_library.dart';
15 |
16 | final messages = new MessageLookup();
17 |
18 | typedef String MessageIfAbsent(String messageStr, List args);
19 |
20 | class MessageLookup extends MessageLookupByLibrary {
21 | String get localeName => 'hi';
22 |
23 | final messages = _notInlinedMessages(_notInlinedMessages);
24 | static Map _notInlinedMessages(_) => {};
25 | }
26 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_ml.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a ml locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
12 |
13 | import 'package:intl/intl.dart';
14 | import 'package:intl/message_lookup_by_library.dart';
15 |
16 | final messages = new MessageLookup();
17 |
18 | typedef String MessageIfAbsent(String messageStr, List args);
19 |
20 | class MessageLookup extends MessageLookupByLibrary {
21 | String get localeName => 'ml';
22 |
23 | final messages = _notInlinedMessages(_notInlinedMessages);
24 | static Map _notInlinedMessages(_) => {};
25 | }
26 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_or.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a or locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
12 |
13 | import 'package:intl/intl.dart';
14 | import 'package:intl/message_lookup_by_library.dart';
15 |
16 | final messages = new MessageLookup();
17 |
18 | typedef String MessageIfAbsent(String messageStr, List args);
19 |
20 | class MessageLookup extends MessageLookupByLibrary {
21 | String get localeName => 'or';
22 |
23 | final messages = _notInlinedMessages(_notInlinedMessages);
24 | static Map _notInlinedMessages(_) => {
25 | "about": MessageLookupByLibrary.simpleMessage("ଵିଷୟରେ"),
26 | "country": MessageLookupByLibrary.simpleMessage("ଦେଶ"),
27 | "dark": MessageLookupByLibrary.simpleMessage("ଗାଢ଼"),
28 | "general": MessageLookupByLibrary.simpleMessage("ସାଧାରଣ"),
29 | "light": MessageLookupByLibrary.simpleMessage("ହାଲୁକା"),
30 | "media": MessageLookupByLibrary.simpleMessage("ମିଡ଼ିଆ"),
31 | "name": MessageLookupByLibrary.simpleMessage("ନାମ"),
32 | "newTrans": MessageLookupByLibrary.simpleMessage("ନୂଆ"),
33 | "no": MessageLookupByLibrary.simpleMessage("ନାହିଁ"),
34 | "ok": MessageLookupByLibrary.simpleMessage("ଠିକ୍ ଅଛି"),
35 | "search": MessageLookupByLibrary.simpleMessage("ସନ୍ଧାନ"),
36 | "system": MessageLookupByLibrary.simpleMessage("ସିଷ୍ଟମ୍"),
37 | "theme": MessageLookupByLibrary.simpleMessage("ଥିମ୍"),
38 | "version": MessageLookupByLibrary.simpleMessage("ସଂସ୍କରଣ"),
39 | "yes": MessageLookupByLibrary.simpleMessage("ହଁ")
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/lib/home/_feed.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:squawker/group/_feed.dart';
3 | import 'package:squawker/group/group_screen.dart';
4 | import 'package:squawker/home/home_screen.dart';
5 | import 'package:squawker/utils/data_service.dart';
6 |
7 |
8 | class FeedScreen extends StatefulWidget {
9 | final ScrollController scrollController;
10 | final String id;
11 | final String name;
12 |
13 | const FeedScreen({Key? key, required this.scrollController, required this.id, required this.name}) : super(key: key);
14 |
15 | @override
16 | State createState() => FeedScreenState();
17 | }
18 |
19 | class FeedScreenState extends State with AutomaticKeepAliveClientMixin {
20 |
21 | Future checkUpdateOrRefreshFeed() async {
22 | if (DataService().map.containsKey('toggleKeepFeed')) {
23 | setState(() {
24 | DataService().map.remove('toggleKeepFeed');
25 | DataService().map['keepFeed'] = false;
26 | updateKeepAlive();
27 | });
28 | }
29 | if (DataService().map.containsKey('toggleRefreshFeed')) {
30 | DataService().map.remove('toggleRefreshFeed');
31 | GlobalKey? sgfKey = DataService().map['feed_key_${widget.id.replaceAll('-', '_')}'];
32 | if (sgfKey?.currentState != null) {
33 | sgfKey!.currentState!.refresh();
34 | }
35 | }
36 | }
37 |
38 | @override
39 | bool get wantKeepAlive {
40 | bool ret = true;
41 | if (DataService().map.containsKey('keepFeed')) {
42 | ret = DataService().map['keepFeed'] as bool;
43 | }
44 | return ret;
45 | }
46 |
47 | @override
48 | Widget build(BuildContext context) {
49 | super.build(context);
50 |
51 | DataService().map['keepFeed'] = true;
52 | return SubscriptionGroupScreen(
53 | scrollController: widget.scrollController,
54 | id: widget.id,
55 | name: widget.name,
56 | actions: createCommonAppBarActions(context));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/home/_groups.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:material_symbols_icons/symbols.dart';
3 | import 'package:squawker/generated/l10n.dart';
4 | import 'package:squawker/group/group_model.dart';
5 | import 'package:squawker/home/home_screen.dart';
6 | import 'package:squawker/subscriptions/_groups.dart';
7 | import 'package:provider/provider.dart';
8 |
9 | class GroupsScreen extends StatelessWidget {
10 | final ScrollController scrollController;
11 |
12 | const GroupsScreen({Key? key, required this.scrollController}) : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | body: NestedScrollView(
18 | controller: scrollController,
19 | headerSliverBuilder: (context, innerBoxIsScrolled) {
20 | return [
21 | SliverAppBar(
22 | pinned: false,
23 | snap: true,
24 | floating: true,
25 | title: Text(L10n.current.groups),
26 | actions: [
27 | PopupMenuButton(
28 | icon: const Icon(Symbols.sort_rounded),
29 | itemBuilder: (context) => [
30 | PopupMenuItem(
31 | value: 'name',
32 | child: Text(L10n.of(context).name),
33 | ),
34 | PopupMenuItem(
35 | value: 'created_at',
36 | child: Text(L10n.of(context).date_created),
37 | ),
38 | ],
39 | onSelected: (value) => context.read().changeOrderSubscriptionGroupsBy(value),
40 | ),
41 | IconButton(
42 | icon: const Icon(Symbols.sort_by_alpha),
43 | onPressed: () => context.read().toggleOrderSubscriptionGroupsAscending(),
44 | ),
45 | ...createCommonAppBarActions(context),
46 | ],
47 | )
48 | ];
49 | },
50 | body: SubscriptionGroups(scrollController: scrollController),
51 | ));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/home/_missing.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:squawker/constants.dart';
3 | import 'package:squawker/generated/l10n.dart';
4 | import 'package:squawker/home/home_screen.dart';
5 | import 'package:squawker/ui/errors.dart';
6 |
7 | class MissingScreen extends StatelessWidget {
8 | const MissingScreen({Key? key}) : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Scaffold(
13 | appBar: AppBar(
14 | actions: createCommonAppBarActions(context),
15 | ),
16 | body: EmojiErrorWidget(
17 | emoji: '🧨',
18 | message: L10n.current.missing_page,
19 | errorMessage: L10n.current.two_home_pages_required,
20 | retryText: L10n.current.choose_pages,
21 | onRetry: () => Navigator.pushNamed(context, routeSettingsHome),
22 | showBackButton: false,
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/home/home_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_triple/flutter_triple.dart';
2 | import 'package:squawker/constants.dart';
3 | import 'package:squawker/generated/l10n.dart';
4 | import 'package:squawker/group/group_model.dart';
5 | import 'package:squawker/home/home_screen.dart';
6 | import 'package:squawker/utils/iterables.dart';
7 | import 'package:pref/pref.dart';
8 |
9 | class HomePage {
10 | final String id;
11 | bool selected;
12 | final NavigationPage page;
13 |
14 | HomePage(this.id, this.selected, this.page);
15 | }
16 |
17 | class HomeModel extends Store> {
18 | final BasePrefService prefs;
19 | final GroupsModel groupsModel;
20 |
21 | HomeModel(this.prefs, this.groupsModel) : super([]) {
22 | groupsModel.observer(onState: (state) async {
23 | await loadPages();
24 | });
25 | }
26 |
27 | Future resetPages() async {
28 | await execute(() async {
29 | prefs.set(optionHomePages, defaultHomePages.map((e) => e.id).toList());
30 | await loadPages();
31 | return state;
32 | });
33 | }
34 |
35 | Future loadPages() async {
36 | await execute(() async {
37 | var saved = prefs.getStringList(optionHomePages) ?? [];
38 |
39 | var available = [
40 | ...defaultHomePages,
41 | ...groupsModel.state
42 | .map((e) => NavigationPage('group-${e.id}', (c) => L10n.of(c).group_name(e.name), e.iconData)),
43 | ];
44 |
45 | var pages = [];
46 |
47 | // First, add all of our saved pages, in the correct order
48 | for (var id in saved) {
49 | var page = available.firstWhereOrNull((e) => e.id == id);
50 | if (page == null) {
51 | continue;
52 | }
53 |
54 | pages.add(HomePage(id, true, page));
55 | }
56 |
57 | // Then add all the other available pages, unselected, to the end of the list, for the settings screen
58 | for (var page in available) {
59 | if (saved.contains(page.id)) {
60 | continue;
61 | }
62 |
63 | pages.add(HomePage(page.id, false, page));
64 | }
65 |
66 | return pages;
67 | });
68 | }
69 |
70 | Future movePage(int oldIndex, int newIndex) async {
71 | if (newIndex > oldIndex) {
72 | newIndex = newIndex - 1;
73 | }
74 |
75 | final page = state.removeAt(oldIndex);
76 | state.insert(newIndex, page);
77 | update(state);
78 | }
79 |
80 | Future save() async {
81 | var pages = state.where((e) => e.selected).map((e) => e.id).toList();
82 |
83 | await prefs.set(optionHomePages, pages);
84 | }
85 |
86 | Future selectPage(String id, bool selected) async {
87 | for (var page in state) {
88 | if (page.id == id) {
89 | page.selected = selected;
90 | break;
91 | }
92 | }
93 |
94 | update(state, force: true);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/import_data_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:squawker/database/entities.dart';
3 | import 'package:squawker/database/repository.dart';
4 | import 'package:logging/logging.dart';
5 | import 'package:sqflite/sqflite.dart';
6 |
7 | class ImportDataModel extends ChangeNotifier {
8 | static final log = Logger('HomeModel');
9 |
10 | Future importData(Map> data) async {
11 | var database = await Repository.writable();
12 |
13 | var batch = database.batch();
14 |
15 | for (var pair in data.entries) {
16 | for (var datum in pair.value) {
17 | batch.insert(pair.key, datum.toMap(), conflictAlgorithm: ConflictAlgorithm.replace);
18 | }
19 |
20 | log.info('Imported data into ${pair.key}');
21 | }
22 |
23 | await batch.commit();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/l10n/intl_hi.arb:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lib/l10n/intl_ml.arb:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/lib/l10n/intl_or.arb:
--------------------------------------------------------------------------------
1 | {
2 | "media": "ମିଡ଼ିଆ",
3 | "@media": {},
4 | "general": "ସାଧାରଣ",
5 | "@general": {},
6 | "theme": "ଥିମ୍",
7 | "@theme": {},
8 | "system": "ସିଷ୍ଟମ୍",
9 | "@system": {},
10 | "light": "ହାଲୁକା",
11 | "@light": {},
12 | "about": "ଵିଷୟରେ",
13 | "@about": {},
14 | "version": "ସଂସ୍କରଣ",
15 | "@version": {},
16 | "no": "ନାହିଁ",
17 | "@no": {},
18 | "yes": "ହଁ",
19 | "@yes": {},
20 | "newTrans": "ନୂଆ",
21 | "@newTrans": {},
22 | "ok": "ଠିକ୍ ଅଛି",
23 | "@ok": {},
24 | "name": "ନାମ",
25 | "@name": {},
26 | "search": "ସନ୍ଧାନ",
27 | "@search": {},
28 | "country": "ଦେଶ",
29 | "@country": {},
30 | "dark": "ଗାଢ଼",
31 | "@dark": {}
32 | }
33 |
--------------------------------------------------------------------------------
/lib/loading.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LoadingStack extends StatelessWidget {
4 | final bool loading;
5 | final Widget child;
6 |
7 | const LoadingStack({Key? key, required this.loading, required this.child}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Stack(
12 | fit: StackFit.loose,
13 | children: [
14 | AnimatedOpacity(
15 | opacity: loading ? 0.0 : 1.0,
16 | duration: const Duration(milliseconds: 500),
17 | child: child,
18 | ),
19 | Center(
20 | child: Padding(
21 | padding: const EdgeInsets.all(64),
22 | child: AnimatedOpacity(
23 | opacity: loading ? 1.0 : 0.0,
24 | duration: const Duration(milliseconds: 200),
25 | child: const CircularProgressIndicator(),
26 | ),
27 | ),
28 | ),
29 | ],
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/models.dart:
--------------------------------------------------------------------------------
1 | class Country {
2 | final String flag;
3 | final String name;
4 |
5 | Country(this.flag, this.name);
6 | }
7 |
8 | class Instance {
9 | final String hostname;
10 | final String country;
11 |
12 | Instance(this.hostname, this.country);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/profile/profile_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_triple/flutter_triple.dart';
2 | import 'package:squawker/client/client.dart';
3 | import 'package:squawker/user.dart';
4 |
5 | class Profile {
6 | final UserWithExtra user;
7 | final List pinnedTweets;
8 |
9 | Profile(this.user, this.pinnedTweets);
10 | }
11 |
12 | class ProfileModel extends Store {
13 | ProfileModel() : super(Profile(UserWithExtra(), []));
14 |
15 | Future loadProfileById(String id) async {
16 | await execute(() async => await Twitter.getProfileById(id));
17 | }
18 |
19 | Future loadProfileByScreenName(String screenName) async {
20 | await execute(() async => await Twitter.getProfileByScreenName(screenName));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/saved/saved_tweet_model.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter_triple/flutter_triple.dart';
4 | import 'package:squawker/database/entities.dart';
5 | import 'package:squawker/database/repository.dart';
6 | import 'package:logging/logging.dart';
7 |
8 | class SavedTweetModel extends Store> {
9 | static final log = Logger('SavedTweetModel');
10 |
11 | SavedTweetModel() : super([]);
12 |
13 | bool isSaved(String id) {
14 | return state.any((e) => e.id == id);
15 | }
16 |
17 | Future deleteSavedTweet(String id) async {
18 | log.info('Deleting tweet with the ID $id');
19 |
20 | await execute(() async {
21 | var database = await Repository.writable();
22 |
23 | await database.delete(tableSavedTweet, where: 'id = ?', whereArgs: [id]);
24 | state.removeWhere((e) => e.id == id);
25 |
26 | update(state, force: true);
27 |
28 | return state;
29 | });
30 | }
31 |
32 | Future listSavedTweets() async {
33 | log.info('Listing saved tweets');
34 |
35 | await execute(() async {
36 | var database = await Repository.readOnly();
37 |
38 | return (await database.query(tableSavedTweet, orderBy: 'saved_at DESC'))
39 | .map((e) => SavedTweet.fromMap(e))
40 | .toList();
41 | });
42 | }
43 |
44 | Future saveTweet(String id, String? user, Map content) async {
45 | log.info('Saving tweet with the ID $id');
46 |
47 | await execute(() async {
48 | var database = await Repository.writable();
49 |
50 | var encodedContent = jsonEncode(content);
51 |
52 | await database.insert(tableSavedTweet, {'id': id, 'user_id': user, 'content': encodedContent});
53 | state.insert(0, SavedTweet(id: id, user: user, content: encodedContent));
54 |
55 | return state;
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/search/search_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_triple/flutter_triple.dart';
2 | import 'package:squawker/client/client.dart';
3 | import 'package:squawker/user.dart';
4 |
5 | class SearchTweetsModel extends Store> {
6 | SearchTweetsModel() : super(SearchStatus(items: []));
7 |
8 | Future searchTweets(String query, bool enhanced, {bool trending = false, String? cursor}) async {
9 | await execute(() async {
10 | if (query.isEmpty) {
11 | return SearchStatus(items: []);
12 | } else {
13 | if (enhanced) {
14 | TweetStatus ts = await Twitter.searchTweetsGraphql(query, true, trending: trending, cursor: cursor);
15 | return SearchStatus(items: ts.chains.map((e) => e.tweets).expand((e) => e).toList(), cursorBottom: ts.cursorBottom);
16 | }
17 | else {
18 | TweetStatus ts = await Twitter.searchTweets(query, true, cursor: cursor, cursorType: cursor != null ? 'cursor_bottom' : null);
19 | return SearchStatus(items: ts.chains.map((e) => e.tweets).expand((e) => e).toList(), cursorBottom: ts.cursorBottom);
20 | }
21 | }
22 | });
23 | }
24 | }
25 |
26 | class SearchUsersModel extends Store> {
27 | SearchUsersModel() : super(SearchStatus(items: []));
28 |
29 | Future searchUsers(String query, bool enhanced, {String? cursor}) async {
30 | await execute(() async {
31 | if (query.isEmpty) {
32 | return SearchStatus(items: []);
33 | } else {
34 | if (enhanced) {
35 | return await Twitter.searchUsersGraphql(query, limit: 100, cursor: cursor);
36 | }
37 | else {
38 | return await Twitter.searchUsers(query, limit: 100);
39 | }
40 | }
41 | });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/settings/_home.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_triple/flutter_triple.dart';
3 | import 'package:material_symbols_icons/symbols.dart';
4 | import 'package:squawker/generated/l10n.dart';
5 | import 'package:squawker/home/home_model.dart';
6 | import 'package:squawker/ui/errors.dart';
7 | import 'package:provider/provider.dart';
8 |
9 | class SettingsHomeFragment extends StatelessWidget {
10 | const SettingsHomeFragment({Key? key}) : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | var model = context.read();
15 |
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: Text(L10n.current.home),
19 | actions: [
20 | IconButton(
21 | icon: const Icon(Symbols.restart_alt),
22 | tooltip: L10n.current.reset_home_pages,
23 | onPressed: () async => await model.resetPages())
24 | ],
25 | ),
26 | body: Padding(
27 | padding: const EdgeInsets.symmetric(vertical: 8),
28 | child: ScopedBuilder>.transition(
29 | store: model,
30 | onError: (_, e) => ScaffoldErrorWidget(
31 | prefix: L10n.current.unable_to_load_home_pages,
32 | error: e,
33 | stackTrace: null,
34 | onRetry: () async => await model.resetPages(),
35 | retryText: L10n.current.reset_home_pages,
36 | ),
37 | onLoading: (_) => const Center(child: CircularProgressIndicator()),
38 | onState: (_, data) {
39 | return ReorderableListView.builder(
40 | itemCount: data.length,
41 | itemBuilder: (context, index) {
42 | var page = data[index];
43 |
44 | return CheckboxListTile(
45 | key: Key(page.id),
46 | secondary: const Icon(Symbols.drag_handle),
47 | title: Text(page.page.titleBuilder(context)),
48 | value: page.selected,
49 | onChanged: (value) async {
50 | var selected = value ?? false;
51 | if (selected == false && data.where((e) => e.selected).length == 2) {
52 | showSnackBar(context,
53 | icon: '🙊', message: L10n.current.you_must_have_at_least_2_home_screen_pages);
54 | return;
55 | }
56 |
57 | await model.selectPage(page.id, value ?? false);
58 | await model.save();
59 | },
60 | );
61 | },
62 | onReorder: (oldIndex, newIndex) async {
63 | await model.movePage(oldIndex, newIndex);
64 | await model.save();
65 | },
66 | );
67 | },
68 | ),
69 | ),
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/subscriptions/subscriptions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:material_symbols_icons/symbols.dart';
3 | import 'package:squawker/generated/l10n.dart';
4 | import 'package:squawker/home/home_screen.dart';
5 | import 'package:squawker/subscriptions/_import.dart';
6 | import 'package:squawker/subscriptions/_list.dart';
7 | import 'package:squawker/subscriptions/users_model.dart';
8 | import 'package:provider/provider.dart';
9 |
10 | class SubscriptionsScreen extends StatelessWidget {
11 | const SubscriptionsScreen({Key? key}) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: Text(L10n.current.subscriptions),
18 | actions: [
19 | IconButton(
20 | icon: const Icon(Symbols.cloud_download_rounded),
21 | onPressed: () =>
22 | showModalBottomSheet(context: context, builder: (BuildContext c) => const SubscriptionImportScreen()),
23 | ),
24 | IconButton(
25 | icon: const Icon(Symbols.refresh_rounded),
26 | onPressed: () async {
27 | await context.read().refreshSubscriptionData();
28 | },
29 | ),
30 | PopupMenuButton(
31 | icon: const Icon(Symbols.sort_rounded),
32 | itemBuilder: (context) => [
33 | PopupMenuItem(
34 | value: 'name',
35 | child: Text(L10n.of(context).name),
36 | ),
37 | PopupMenuItem(
38 | value: 'screen_name',
39 | child: Text(L10n.of(context).username),
40 | ),
41 | PopupMenuItem(
42 | value: 'created_at',
43 | child: Text(L10n.of(context).date_subscribed),
44 | ),
45 | ],
46 | onSelected: (value) => context.read().changeOrderSubscriptionsBy(value),
47 | ),
48 | IconButton(
49 | icon: const Icon(Symbols.sort_by_alpha_rounded),
50 | onPressed: () async {
51 | await context.read().toggleOrderSubscriptionsAscending();
52 | },
53 | ),
54 | ...createCommonAppBarActions(context)
55 | ],
56 | ),
57 | body: const SubscriptionUsers(),
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/trends/trends.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:material_symbols_icons/symbols.dart';
3 | import 'package:squawker/trends/_list.dart';
4 | import 'package:squawker/trends/_settings.dart';
5 | import 'package:squawker/trends/_tabs.dart';
6 |
7 | class TrendsScreen extends StatefulWidget {
8 | const TrendsScreen({Key? key}) : super(key: key);
9 |
10 | @override
11 | State createState() => _TrendsScreenState();
12 | }
13 |
14 | class _TrendsScreenState extends State with AutomaticKeepAliveClientMixin {
15 | @override
16 | bool get wantKeepAlive => true;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | super.build(context);
21 |
22 | return Scaffold(
23 | appBar: const TrendsTabBar(),
24 | floatingActionButton: FloatingActionButton(
25 | child: const Icon(Symbols.add_rounded),
26 | onPressed: () async => showModalBottomSheet(
27 | context: context,
28 | builder: (context) => const TrendsSettings(),
29 | )),
30 | body: TrendsList(),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/tweet/_context_menu.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'package:flutter/material.dart';
3 |
4 | Widget customContextMenuBuilder(
5 | BuildContext context,
6 | EditableTextState editableTextState,
7 | List items,
8 | Future Function(int index, String value, bool readonly) callback,
9 | ) {
10 | var buttonItems = editableTextState.contextMenuButtonItems;
11 | var textEditingValue = editableTextState.textEditingValue;
12 | var selection = textEditingValue.selection;
13 |
14 | if (!selection.isCollapsed) {
15 | var value = selection.textInside(textEditingValue.text);
16 | for (var i = 0; i < items.length; i++) {
17 | buttonItems.add(ContextMenuButtonItem(
18 | onPressed: () async {
19 | editableTextState.hideToolbar();
20 | var readOnly = editableTextState.widget.readOnly;
21 | var newValue = await callback(i, value, readOnly);
22 |
23 | int offset = max(selection.baseOffset, selection.extentOffset);
24 | var newTextEditingValue = textEditingValue.copyWith(
25 | selection: TextSelection.collapsed(offset: offset),
26 | );
27 | if (!readOnly && newValue != null) {
28 | newTextEditingValue = newTextEditingValue.replaced(
29 | selection,
30 | newValue,
31 | );
32 | }
33 |
34 | editableTextState.userUpdateTextEditingValue(
35 | newTextEditingValue,
36 | SelectionChangedCause.toolbar,
37 | );
38 | },
39 | label: items[i],
40 | ));
41 | }
42 | }
43 |
44 | return AdaptiveTextSelectionToolbar.buttonItems(
45 | buttonItems: buttonItems,
46 | anchors: editableTextState.contextMenuAnchors,
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/lib/tweet/_entities.dart:
--------------------------------------------------------------------------------
1 | import 'package:dart_twitter_api/twitter_api.dart';
2 | import 'package:flutter/gestures.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | abstract class TweetEntity {
6 | List? indices;
7 |
8 | TweetEntity(this.indices);
9 |
10 | InlineSpan getContent();
11 |
12 | int getEntityStart() {
13 | return indices![0];
14 | }
15 |
16 | int getEntityEnd() {
17 | return indices![1];
18 | }
19 | }
20 |
21 | class TweetHashtag extends TweetEntity {
22 | final Hashtag hashtag;
23 | final Function onTap;
24 |
25 | TweetHashtag(this.hashtag, this.onTap) : super(hashtag.indices);
26 |
27 | @override
28 | InlineSpan getContent() {
29 | return TextSpan(
30 | text: '#${hashtag.text}',
31 | style: const TextStyle(color: Colors.blue),
32 | recognizer: TapGestureRecognizer()
33 | ..onTap = () {
34 | onTap();
35 | });
36 | }
37 | }
38 |
39 | class TweetUserMention extends TweetEntity {
40 | final UserMention mention;
41 | final Function onTap;
42 |
43 | TweetUserMention(this.mention, this.onTap) : super(mention.indices);
44 |
45 | @override
46 | InlineSpan getContent() {
47 | return TextSpan(
48 | text: '@${mention.screenName}',
49 | style: const TextStyle(color: Colors.blue),
50 | recognizer: TapGestureRecognizer()
51 | ..onTap = () {
52 | onTap();
53 | });
54 | }
55 | }
56 |
57 | class TweetUrl extends TweetEntity {
58 | final Url url;
59 | final Function onTap;
60 |
61 | TweetUrl(this.url, this.onTap) : super(url.indices);
62 |
63 | @override
64 | InlineSpan getContent() {
65 | return TextSpan(
66 | text: url.displayUrl,
67 | style: const TextStyle(color: Colors.blue),
68 | recognizer: TapGestureRecognizer()
69 | ..onTap = () {
70 | onTap();
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/tweet/conversation.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:squawker/client/client.dart';
3 | import 'package:squawker/tweet/tweet.dart';
4 | import 'package:squawker/utils/iterables.dart';
5 |
6 | class TweetConversation extends StatefulWidget {
7 | final String id;
8 | final String? username;
9 | final bool isPinned;
10 | final List tweets;
11 | final Map? tweetIdxDic;
12 | final VisiblePositionState? visiblePositionState;
13 |
14 | const TweetConversation(
15 | {Key? key, required this.id, required this.username, required this.isPinned, required this.tweets, this.tweetIdxDic, this.visiblePositionState})
16 | : super(key: key);
17 |
18 | @override
19 | State createState() => _TweetConversationState();
20 | }
21 |
22 | class _TweetConversationState extends State {
23 | @override
24 | Widget build(BuildContext context) {
25 | if (widget.tweets.length == 1) {
26 | return TweetTile(
27 | conversationId: widget.id, clickable: true, tweet: widget.tweets.first, currentUsername: widget.username, isPinned: widget.isPinned, tweetIdx: widget.tweetIdxDic == null ? null : widget.tweetIdxDic![widget.tweets.first.idStr], visiblePositionState: widget.visiblePositionState);
28 | }
29 |
30 | var tiles = [];
31 | var tweets = widget.tweets;
32 |
33 | // We need to do a simple for loop so we can mark the first item as the thread start
34 | for (var i = 0; i < tweets.length; i++) {
35 | tiles.add(TweetTile(
36 | conversationId: widget.id,
37 | clickable: true,
38 | tweet: tweets[i],
39 | currentUsername: widget.username,
40 | isPinned: widget.isPinned,
41 | isThread: i == 0,
42 | tweetIdx: widget.tweetIdxDic == null ? null : widget.tweetIdxDic![tweets[i].idStr],
43 | visiblePositionState: widget.visiblePositionState)
44 | );
45 | }
46 |
47 | return Container(
48 | margin: const EdgeInsets.symmetric(horizontal: 4),
49 | decoration: const BoxDecoration(border: Border(left: BorderSide(color: Colors.white, width: 4))),
50 | child: Column(
51 | children: [
52 | ...tiles,
53 | ],
54 | ),
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/tweet/tweet_exceptions.dart:
--------------------------------------------------------------------------------
1 | class TweetMissingDataException implements Exception {
2 | final String? id;
3 | final List missingFields;
4 |
5 | TweetMissingDataException(this.id, this.missingFields);
6 |
7 | @override
8 | String toString() {
9 | return 'TweetMissingDataException{id: $id, missingFields: $missingFields}';
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/ui/dates.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:timeago/timeago.dart' as timeago;
4 |
5 | final absoluteDateFormat = DateFormat.yMMMd().add_Hms();
6 |
7 | String createRelativeDate(DateTime dateTime) {
8 | return timeago.format(dateTime, locale: Intl.shortLocale(Intl.getCurrentLocale()));
9 | }
10 |
11 | class Timestamp extends StatefulWidget {
12 | final DateTime? timestamp;
13 |
14 | const Timestamp({Key? key, required this.timestamp}) : super(key: key);
15 |
16 | @override
17 | State createState() => _TimestampState();
18 | }
19 |
20 | class _TimestampState extends State {
21 | bool _useRelativeTimestamp = true;
22 |
23 | String formattedTime = '';
24 |
25 | @override
26 | void initState() {
27 | super.initState();
28 |
29 | var timestamp = widget.timestamp;
30 | if (timestamp != null) {
31 | formattedTime = createRelativeDate(timestamp);
32 | }
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | var timestamp = widget.timestamp;
38 | if (timestamp == null) {
39 | return Container();
40 | }
41 |
42 | return GestureDetector(
43 | child: Text(formattedTime),
44 | onTap: () {
45 | setState(() {
46 | if (_useRelativeTimestamp) {
47 | formattedTime = createRelativeDate(timestamp);
48 | } else {
49 | formattedTime = absoluteDateFormat.format(timestamp.toLocal());
50 | }
51 |
52 | _useRelativeTimestamp = !_useRelativeTimestamp;
53 | });
54 | },
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/ui/physics.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// The default PageView scroll physics are very sensitive, and easily swipe pages when you mean to scroll up and down
4 | /// instead. This dampens the physics, by making the widget "heavy" (mass), so it's harder to swipe.
5 | class LessSensitiveScrollPhysics extends ScrollPhysics {
6 | const LessSensitiveScrollPhysics({ScrollPhysics? parent}) : super(parent: parent);
7 |
8 | @override
9 | LessSensitiveScrollPhysics applyTo(ScrollPhysics? ancestor) {
10 | return LessSensitiveScrollPhysics(parent: buildParent(ancestor));
11 | }
12 |
13 | @override
14 | SpringDescription get spring => const SpringDescription(
15 | mass: 50,
16 | stiffness: 100,
17 | damping: 1,
18 | );
19 | }
--------------------------------------------------------------------------------
/lib/utils/accent_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flex_color_scheme/flex_color_scheme.dart';
2 | import 'package:flutter/foundation.dart' show defaultTargetPlatform;
3 | import 'package:flutter/material.dart';
4 | import 'package:logging/logging.dart';
5 | import 'package:system_theme/system_theme.dart';
6 |
7 | class AccentUtil {
8 | static final log = Logger('AccentUtil');
9 |
10 | static SystemAccentColor? currentAccentColor;
11 | static FlexSchemeColor? lightAccentColors;
12 | static FlexSchemeColor? darkAccentColors;
13 |
14 | static Future load() async {
15 | if (defaultTargetPlatform.supportsAccentColor) {
16 | await SystemTheme.accentColor.load();
17 | currentAccentColor = SystemTheme.accentColor;
18 | setupFlexColors(currentAccentColor!.accent);
19 | log.info('System accent color is $currentAccentColor.');
20 | SystemTheme.onChange.listen((SystemAccentColor event) {
21 | log.info('System accent color changed to ${event.accent}.');
22 | currentAccentColor = event;
23 | setupFlexColors(currentAccentColor!.accent);
24 | });
25 | }
26 | else {
27 | log.info('Accent color not support by system.');
28 | }
29 | }
30 |
31 | static void setupFlexColors(Color accentColor) {
32 | lightAccentColors = FlexSchemeColor.from(primary: accentColor, brightness: Brightness.light);
33 | darkAccentColors = FlexSchemeColor.from(primary: accentColor, brightness: Brightness.dark);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/lib/utils/cache.dart:
--------------------------------------------------------------------------------
1 | import 'package:ffcache/ffcache.dart';
2 | import 'package:logging/logging.dart';
3 |
4 | extension CacheHelper on FFCache {
5 | static final log = Logger('CacheHelper');
6 |
7 | Future getOrCreateAsJSON(String key, Duration expiry, Future Function() create) async {
8 | var exists = await has(key);
9 | if (exists) {
10 | log.info('Loading $key from the cache');
11 |
12 | return await getJSON(key);
13 | }
14 |
15 | log.info('Loading $key from the source');
16 |
17 | var result = await create();
18 |
19 | await setJSONWithTimeout(key, result, expiry);
20 |
21 | return result;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/utils/crypto_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:math';
3 | import 'package:cryptography/cryptography.dart';
4 | import 'package:hex/hex.dart';
5 |
6 | const String oauthConsumerKey = '3nVuSoBZnx6U4vzUxf5w';
7 | const String oauthConsumerSecret = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys';
8 |
9 | Future hmacSHA1(String key, String text) async {
10 | final hmac = Hmac.sha1();
11 | final mac = await hmac.calculateMac(
12 | utf8.encode(text),
13 | secretKey: SecretKey(utf8.encode(key)),
14 | );
15 | return base64.encode(mac.bytes);
16 | }
17 |
18 | String nonce() {
19 | Random rnd = Random();
20 | List values = List.generate(32, (i) => rnd.nextInt(256));
21 | return base64.encode(values).replaceAll(RegExp('[=/+]'), '');
22 | }
23 |
24 | Future aesGcm256Encrypt(String key, String text) async {
25 | final algorithm = AesGcm.with256bits();
26 | final keyAlgorithm = Sha256();
27 | final hash = await keyAlgorithm.hash(utf8.encode(key));
28 | final secretKey = SecretKey(hash.bytes);
29 | final nonce = algorithm.newNonce();
30 |
31 | final secretBox = await algorithm.encrypt(
32 | utf8.encode(text),
33 | secretKey: secretKey,
34 | nonce: nonce,
35 | );
36 | return base64.encode(secretBox.concatenation());
37 | }
38 |
39 | Future aesGcm256Decrypt(String key, String encryptedText) async {
40 | final algorithm = AesGcm.with256bits();
41 | final keyAlgorithm = Sha256();
42 | final hash = await keyAlgorithm.hash(utf8.encode(key));
43 | final secretKey = SecretKey(hash.bytes);
44 |
45 | final secretBox = SecretBox.fromConcatenation(
46 | base64.decode(encryptedText),
47 | nonceLength: algorithm.nonceLength,
48 | macLength: algorithm.macAlgorithm.macLength
49 | );
50 | final decryptedText = await algorithm.decrypt(secretBox, secretKey: secretKey);
51 | return utf8.decode(decryptedText);
52 | }
53 |
54 | Future sha1Hash(String text) async {
55 | final algorithm = Sha1();
56 | final hash = await algorithm.hash(text.codeUnits);
57 | return HEX.encode(hash.bytes);
58 | }
59 |
--------------------------------------------------------------------------------
/lib/utils/data_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:collection';
2 |
3 | class DataService {
4 |
5 | final Map map = {};
6 |
7 | static final DataService _instance = DataService._();
8 |
9 | factory DataService() {
10 | return _instance;
11 | }
12 |
13 | DataService._();
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/lib/utils/debounce.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | // Based on easy_debounce by magnuswikihog, released under the MIT License: https://github.com/magnuswikhog/easy_debounce
6 | typedef DebounceCallback = VoidCallback;
7 |
8 | class _DebounceTask {
9 | Timer timer;
10 | DebounceCallback callback;
11 |
12 | _DebounceTask(this.callback, this.timer);
13 | }
14 |
15 | class Debouncer {
16 | static final Map _tasks = {};
17 |
18 | static void debounce(String id, Duration duration, DebounceCallback callback) {
19 | _tasks[id]?.timer.cancel();
20 |
21 | _tasks[id] = _DebounceTask(
22 | callback,
23 | Timer(duration, () {
24 | _tasks[id]?.timer.cancel();
25 | _tasks.remove(id);
26 |
27 | callback();
28 | }));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/utils/iterables.dart:
--------------------------------------------------------------------------------
1 | extension Iterables on Iterable {
2 | E? get firstOrNull {
3 | if (isEmpty) {
4 | return null;
5 | }
6 |
7 | return first;
8 | }
9 |
10 | E? firstWhereOrNull(bool Function(E element) test) {
11 | for (var element in this) {
12 | if (test(element)) {
13 | return element;
14 | }
15 | }
16 | return null;
17 | }
18 |
19 | Map> groupBy(K Function(E) keyFunction) => fold(>{},
20 | (Map> map, E element) => map..putIfAbsent(keyFunction(element), () => []).add(element));
21 |
22 | Iterable getRange(int start, [int? end]) {
23 | return (end != null ? take(end) : this).skip(start);
24 | }
25 |
26 | Iterable sorted(int Function(E a, E b) compare) => [...this]..sort(compare);
27 | }
28 |
29 | extension MapWithIndex on List {
30 | List mapWithIndex(R Function(T, int i) callback) {
31 | List result = [];
32 | for (int i = 0; i < length; i++) {
33 | R item = callback(this[i], i);
34 | result.add(item);
35 | }
36 | return result;
37 | }
38 | }
--------------------------------------------------------------------------------
/lib/utils/notifiers.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CombinedChangeNotifier extends ChangeNotifier {
4 | final ChangeNotifier one;
5 | final ChangeNotifier two;
6 |
7 | CombinedChangeNotifier(this.one, this.two) {
8 | one.addListener(() => notifyListeners());
9 | two.addListener(() => notifyListeners());
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/utils/route_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:squawker/utils/data_service.dart';
3 |
4 | Future pushNamedRoute(BuildContext context, String routeName, Object? arguments) async {
5 | DataService().map[routeName] = arguments;
6 | return Navigator.pushNamed(context, routeName);
7 | }
8 |
9 | Object? getNamedRouteArguments(String routeName, {bool removeArgumentsFromSession = true}) {
10 | Object? args = DataService().map[routeName];
11 | if (removeArgumentsFromSession) {
12 | DataService().map.remove(routeName);
13 | }
14 | return args;
15 | }
16 |
--------------------------------------------------------------------------------
/lib/utils/share_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:typed_data';
3 | import 'package:path_provider/path_provider.dart';
4 | import 'package:share_plus/share_plus.dart';
5 | import 'package:uuid/uuid.dart';
6 |
7 | // The following is a workaround because of an issue with the share_plus package which uses the faulty mime_type library.
8 | // When the issue is resolved (the PR https://github.com/dart-lang/mime/pull/81 is merged),
9 | // then it should be replaced by the original code:
10 | // Share.shareXFiles([XFile.fromData(fileBytes, mimeType: 'image/jpeg')]);
11 | Future shareJpegData(Uint8List data) async {
12 | const uuid = Uuid();
13 |
14 | final String tempPath = (await getTemporaryDirectory()).path;
15 | final name = uuid.v4();
16 | final path = '$tempPath/$name.jpg';
17 |
18 | final file = File(path);
19 | await file.writeAsBytes(data);
20 |
21 | final xFile = XFile(path, mimeType: 'image/jpeg');
22 |
23 | Share.shareXFiles([xFile]).then((value) => file.delete());
24 | }
25 |
--------------------------------------------------------------------------------
/lib/utils/ui_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | Size calcTextSizeWithStyle(BuildContext context, String text, TextStyle style) {
4 | final TextPainter textPainter = TextPainter(
5 | text: TextSpan(text: text, style: style),
6 | textDirection: TextDirection.ltr,
7 | textScaleFactor: MediaQuery.of(context).textScaleFactor,
8 | )..layout();
9 | return textPainter.size;
10 | }
11 |
12 | Size calcTextSize(BuildContext context, String text) {
13 | return calcTextSizeWithStyle(context, text, DefaultTextStyle.of(context).style);
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/lib/utils/urls.dart:
--------------------------------------------------------------------------------
1 | import 'package:url_launcher/url_launcher_string.dart';
2 |
3 | Future openUri(String uri) async {
4 | await launchUrlString(uri, mode: LaunchMode.externalApplication);
5 | }
--------------------------------------------------------------------------------