├── .flutter-plugins
├── .flutter-plugins-dependencies
├── .gitignore
├── .metadata
├── .pubignore
├── CHANGELOG.md
├── LICENSE
├── PRIVACY-AND-POLICY.MD
├── README.md
├── TERMS-AND-CONDITIONS.MD
├── analysis_options.yaml
├── assets
├── add.svg
├── check.svg
├── completed-mark.svg
├── cover.png
├── light-bulb.svg
├── uo-dark.svg
├── uo.svg
├── upvote-off.svg
└── upvote-on.svg
├── example
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── example
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── 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
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ │ └── LaunchImage.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ └── README.md
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ └── RunnerTests
│ │ └── RunnerTests.swift
├── lib
│ └── main.dart
├── pubspec.lock
└── pubspec.yaml
├── lib
├── src
│ ├── logic
│ │ ├── l10n.dart
│ │ ├── user_orient.dart
│ │ └── user_orient_data.dart
│ ├── models
│ │ ├── endpoint.dart
│ │ ├── feature.dart
│ │ ├── label.dart
│ │ ├── project.dart
│ │ └── user.dart
│ ├── utilities
│ │ ├── build_context_extensions.dart
│ │ ├── helper_functions.dart
│ │ ├── navigation.dart
│ │ └── restful_endpoints.dart
│ ├── views
│ │ ├── board_view.dart
│ │ ├── form_view.dart
│ │ └── sent_view.dart
│ └── widgets
│ │ ├── bottom_padding.dart
│ │ ├── button.dart
│ │ ├── feature_card.dart
│ │ ├── image_fade.dart
│ │ ├── styled_close_button.dart
│ │ ├── styled_text_field.dart
│ │ ├── tip_card.dart
│ │ └── watermark.dart
└── userorient_flutter.dart
├── pubspec.yaml
└── test
└── userorient_flutter_test.dart
/.flutter-plugins:
--------------------------------------------------------------------------------
1 | # This is a generated file; do not edit or check into version control.
2 | path_provider_linux=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
3 | path_provider_windows=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/
4 | shared_preferences=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences-2.5.2/
5 | shared_preferences_android=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.6/
6 | shared_preferences_foundation=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/
7 | shared_preferences_linux=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/
8 | shared_preferences_web=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/
9 | shared_preferences_windows=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/
10 | url_launcher=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher-6.3.1/
11 | url_launcher_android=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.14/
12 | url_launcher_ios=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/
13 | url_launcher_linux=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/
14 | url_launcher_macos=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.2/
15 | url_launcher_web=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.0/
16 | url_launcher_windows=/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/
17 |
--------------------------------------------------------------------------------
/.flutter-plugins-dependencies:
--------------------------------------------------------------------------------
1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"shared_preferences_foundation","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"shared_preferences_android","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.14/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"shared_preferences_foundation","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"shared_preferences_web","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/kamranbekirov/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2025-06-02 02:09:54.178641","version":"3.32.0","swift_package_manager_enabled":{"ios":false,"macos":false}}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | build/
30 |
--------------------------------------------------------------------------------
/.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: "300451adae589accbece3490f4396f10bdf15e6e"
8 | channel: "stable"
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/.pubignore:
--------------------------------------------------------------------------------
1 | assets/cover.png
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.1.0
2 |
3 | - Web support is here 🎉 . The board now works on Flutter for Web apps.
4 | - Improved opening transitions on Android and iOS to be more consistent with native apps.
5 |
6 | ## 1.0.0
7 |
8 | - 1.0.0 is here 🎉 . This means after a long time of internal testing and polishing, UserOrient is now ready for production.
9 | - Also, fixed overflow issue in the form view when the keyboard is open.
10 | - Restricted submitting empty form.
11 |
12 | ## 0.3.2
13 |
14 | - Fix issue where labels in Spanish were having null values, causing Text widget to throw an error.
15 | - Improve localization texts.
16 |
17 | ## 0.3.1
18 |
19 | - Localize tab labels.
20 |
21 | ## 0.3.0
22 |
23 | - Add support for dark mode.
24 | - Add "Roadmap" and "Implemented" tabs to better organize the features list.
25 | - Fix language issue where only English was supported (by mistake).
26 |
27 | ## 0.2.1
28 |
29 | - Make English the default language in case the language is not supported.
30 | - Refresh the features list when the board is re-opened.
31 | - Upgrade dependencies.
32 |
33 | ## 0.2.0
34 |
35 | - Fix pub.dev score issues and update documentation.
36 |
37 | ## 0.1.9
38 |
39 | - Update documentation.
40 |
41 | ## 0.1.8
42 |
43 | - Fix issue where all first letters of words in request message were being capitalized.
44 |
45 | ## 0.1.7
46 |
47 | - Update design of the board.
48 |
49 | ## 0.1.6
50 |
51 | - Display tags of the features.
52 |
53 | ## 0.1.5
54 |
55 | - Redesigned the board with minimalistic design.
56 |
57 | ## 0.1.4
58 |
59 | - Improve documentation.
60 | - Make `UserOrient.openBoard(...)` method a `Future`.
61 |
62 | ## 0.1.3
63 |
64 | - Add English language support.
65 |
66 | ## 0.1.2
67 |
68 | - Seems like the previous bug fix didn't work. Fixed it again.
69 |
70 | ## 0.1.1
71 |
72 | - Fixed a bug where multiple users were being created on the server.
73 |
74 | ## 0.1.0
75 |
76 | - Improved the board for better user experience.
77 |
78 | ## 0.0.9
79 |
80 | - Update Flutter SDK version to 2.17.0
81 |
82 | ## 0.0.8
83 |
84 | - Improved the board for better user experience.
85 |
86 | ## 0.0.7
87 |
88 | - Improved documentation and board UI.
89 |
90 | ## 0.0.6
91 |
92 | - Cached user UUID to prevent unnecessary API calls on second launch.
93 |
94 | ## 0.0.5
95 |
96 | - Seems like the previous bug fix didn't work. Fixed it again.
97 |
98 | ## 0.0.4
99 |
100 | - Fix bug when projectId changes
101 |
102 | ## 0.0.3
103 |
104 | - Set user details using `UserOrient.setUser()` method
105 | - Beautify the board interface
106 | - Fix initialization bug
107 |
108 | ## 0.0.2
109 |
110 | - Added new header style for board.
111 | - Couple of improvements to the package.
112 |
113 | ## 0.0.1
114 |
115 | - Initial release of the package.
116 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2024, UserOrient
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/PRIVACY-AND-POLICY.MD:
--------------------------------------------------------------------------------
1 | # Privacy Policy
2 |
3 | **Effective Date: 25 October 2024**
4 |
5 | UserOrient values your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our service. By accessing UserOrient, you agree to the practices described here.
6 |
7 | ## 1. Information We Collect
8 |
9 | ### Personal Data
10 | We may collect:
11 | - **Account Information**: email address upon signup.
12 | - **Usage Data**: Data on how you interact with UserOrient, such as feature requests, voting activities.
13 |
14 | ### Non-Personal Data
15 | - **Cookies and Analytics**: Information about your device and browsing actions, gathered for analytics and service improvement.
16 |
17 | ## 2. How We Use Your Information
18 |
19 | Collected data is used for:
20 | - Providing and improving the UserOrient service,
21 | - Communicating updates, feature announcements, and support,
22 | - Analyzing usage trends to improve user experience.
23 |
24 | ## 3. Sharing Your Information
25 |
26 | - **Service Providers**: We work with GDPR-compliant third parties to process payments, manage analytics, and support technical operations. Data shared with these providers is limited to what is necessary for service improvement.
27 | - **Legal Requirements**: UserOrient may disclose data if required by law or to protect the security and rights of our users or the public.
28 |
29 | ## 4. Your Rights
30 |
31 | ### Under GDPR and similar regulations, you have the right to:
32 | - **Access**: Request a copy of your data.
33 | - **Correction**: Update or correct personal data.
34 | - **Deletion**: Request deletion of your account and data, with certain legal exceptions.
35 | - **Withdraw Consent**: For data processing based on consent, you can withdraw consent at any time by contacting us.
36 |
37 | Requests should be directed to: **[kamran@userorient.com](mailto:kamran@userorient.com)**
38 |
39 | ## 5. Data Security
40 |
41 | We use industry-standard practices to protect your data, including encryption and secure data storage. However, no method is 100% secure. Please secure your UserOrient account credentials.
42 |
43 | ## 6. Data Retention
44 |
45 | We retain personal data as long as necessary for service provision or as required by law. Upon account deletion, personal data will be removed from our active systems.
46 |
47 | ## 7. International Data Transfers
48 |
49 | UserOrient operates globally. Data may be stored or processed in jurisdictions with differing privacy laws. We ensure appropriate safeguards for cross-border data transfers, as required by GDPR and other regulations.
50 |
51 | ## 8. Children’s Privacy
52 |
53 | UserOrient is not intended for use by individuals under 16 years old. We do not knowingly collect data from minors. If you believe we have collected such data, contact us to delete it.
54 |
55 | ## 9. Changes to This Policy
56 |
57 | We may update this Privacy Policy periodically. Major updates will be communicated via email or app notifications. The “Effective Date” at the top indicates when changes take effect.
58 |
59 | ## 10. Contact Us
60 |
61 | For questions or requests regarding this Privacy Policy, please contact us at:
62 | - **Email**: [kamran@userorient.com](mailto:kamran@userorient.com)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [UserOrient.com](https://userorient.com)
2 |
3 | [](https://pub.dartlang.org/packages/userorient_flutter)
4 | [](https://pub.dev/packages/userorient_flutter/score)
5 | [](https://pub.dev/packages/userorient_flutter/score)
6 | [](https://pub.dev/packages/userorient_flutter/score)
7 |
8 | **Feature Voting Board for Flutter**
9 |
10 | UserOrient is a feature voting board that helps you collect feedback from your users and prioritize your development roadmap in your Flutter projects.
11 |
12 |
13 |
14 |
15 |
16 | ## Getting Started
17 |
18 | Considering that you have already created a project on [UserOrient.com](https://userorient.com) and received an API key, follow these steps to integrate the SDK into your Flutter app.
19 |
20 | ### Add the dependency
21 |
22 | Add the following to your `pubspec.yaml` file:
23 |
24 | ```yaml
25 | dependencies:
26 | userorient_flutter:
27 | ```
28 |
29 | ### Initialize the SDK
30 |
31 | Initialize the SDK with your project's API key and preferred language:
32 |
33 | ```dart
34 | import 'package:userorient_flutter/userorient_flutter.dart';
35 |
36 | void main() {
37 | UserOrient.configure(
38 | apiKey: 'YOUR_API_KEY',
39 | languageCode: 'en',
40 | );
41 | ```
42 |
43 | ### Display the board
44 |
45 | To show the UserOrient board, call `UserOrient.openBoard(context)`:
46 |
47 | ```dart
48 | import 'package:userorient_flutter/userorient_flutter.dart';
49 |
50 | void openBoard() {
51 | // Set user information
52 | UserOrient.setUser(
53 | uniqueIdentifier: '123456',
54 | fullName: 'Kamran Bekirov',
55 | email: 'kamran@userorient.com',
56 | phoneNumber: '+1234567890',
57 | language: 'en',
58 | extra: {
59 | 'age': 27,
60 | 'is_premium': true,
61 | }
62 | );
63 |
64 | // Display the board
65 | UserOrient.openBoard(context);
66 | }
67 | ```
68 |
69 | > **Note:** It's recommended to call `UserOrient.setUser` before each board launch to ensure up-to-date user information.
70 |
71 | ## User Identification
72 |
73 | UserOrient requires a unique identifier (`uniqueIdentifier`) for each user. This can be an email address, phone number, or custom ID. If not provided, UserOrient will generate a random identifier.
74 |
75 | ## Logging Out
76 |
77 | When a user logs out of your app, call `UserOrient.clearCache()` to prevent potential issues:
78 |
79 | ```dart
80 | await UserOrient.clearCache();
81 | ```
82 |
83 | ## Contact
84 |
85 | For any questions or support, please reach out to us:
86 |
87 | - Email: [kamran@userorient.com](mailto:kamran@userorient.com)
88 | - Twitter: [@userorient](https://twitter.com/userorient)
89 |
90 | ---
91 |
92 |
93 | Developed by Kamran Bekirov , por el arte
94 |
95 |
--------------------------------------------------------------------------------
/TERMS-AND-CONDITIONS.MD:
--------------------------------------------------------------------------------
1 | # Terms and Conditions
2 |
3 | **Effective Date: 24 October 2024**
4 |
5 | Welcome to UserOrient! By using our service, you agree to these Terms and Conditions. Please read them carefully.
6 |
7 | ## 1. Service Overview
8 | UserOrient provides a platform for voting on feature requests, particularly for Flutter projects. Our goal is to enhance software development through user-driven feedback.
9 |
10 | ## 2. User Accounts
11 | - **Registration**: Users must provide accurate information and maintain the security of their accounts.
12 | - **Eligibility**: UserOrient is for individuals aged 16 and above. Use by minors is not permitted.
13 |
14 | ## 3. Subscriptions and Payments
15 | - **Payment Terms**: Subscription fees are billed monthly or annually as selected.
16 | - **Cancellation**: Users may cancel subscriptions through their account settings, effective at the end of the current billing cycle.
17 |
18 | ## 4. User Conduct
19 | Users agree to interact respectfully and avoid:
20 | - Posting offensive or harmful content,
21 | - Violating intellectual property rights,
22 | - Engaging in illegal activities or misuse of the platform.
23 |
24 | ## 5. Content Ownership and License
25 | - **User Content**: Users retain ownership of content they submit, such as feature requests and comments.
26 | - **License to UserOrient**: By submitting content, users grant UserOrient a license to use and display it within the platform.
27 |
28 | ## 6. Limitation of Liability
29 | UserOrient is provided on an "as-is" basis. We disclaim liability for indirect damages, including data loss or service interruptions.
30 |
31 | ## 7. Privacy and Data Use
32 | Our Privacy Policy outlines how we handle user data. By using UserOrient, you agree to the collection and use of data as described.
33 |
34 | ## 8. Modifications to Terms
35 | We may update these Terms periodically. Changes will be posted, and users will be notified of significant updates.
36 |
37 | ## 9. Governing Law
38 | These Terms will be governed by applicable laws based on our place of incorporation.
39 |
40 | ## 10. Contact Us
41 | For any questions, reach out to us at:
42 | - **Email**: [kamran@userorient.com](mailto:kamran@userorient.com)
43 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 | analyzer:
3 | errors:
4 | deprecated_member_use: ignore
5 |
6 | # Additional information about this file can be found at
7 | # https://dart.dev/guides/language/analysis-options
8 |
--------------------------------------------------------------------------------
/assets/add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/completed-mark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/assets/cover.png
--------------------------------------------------------------------------------
/assets/light-bulb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/uo-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/assets/uo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/assets/upvote-off.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/upvote-on.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .pub-cache/
33 | .pub/
34 | /build/
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Android Studio will place build artifacts here
43 | /android/app/debug
44 | /android/app/profile
45 | /android/app/release
46 |
--------------------------------------------------------------------------------
/example/.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: "300451adae589accbece3490f4396f10bdf15e6e"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
17 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
18 | - platform: android
19 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
20 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
21 | - platform: ios
22 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
23 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
24 | - platform: linux
25 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
26 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
27 | - platform: macos
28 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
29 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
30 | - platform: web
31 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
32 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
33 | - platform: windows
34 | create_revision: 300451adae589accbece3490f4396f10bdf15e6e
35 | base_revision: 300451adae589accbece3490f4396f10bdf15e6e
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | A new Flutter project.
4 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
--------------------------------------------------------------------------------
/example/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 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "com.example.example"
27 | compileSdk flutter.compileSdkVersion
28 | ndkVersion flutter.ndkVersion
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 |
43 | defaultConfig {
44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
45 | applicationId "com.example.example"
46 | // You can update the following values to match your application needs.
47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
48 | minSdkVersion flutter.minSdkVersion
49 | targetSdkVersion flutter.targetSdkVersion
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | }
53 |
54 | buildTypes {
55 | release {
56 | // TODO: Add your own signing config for the release build.
57 | // Signing with the debug keys for now, so `flutter run --release` works.
58 | signingConfig signingConfigs.debug
59 | }
60 | }
61 | }
62 |
63 | flutter {
64 | source '../..'
65 | }
66 |
67 | dependencies {}
68 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/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 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/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 |
--------------------------------------------------------------------------------
/example/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.7.10" apply false
24 | }
25 |
26 | include ":app"
27 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.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 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - shared_preferences_foundation (0.0.1):
4 | - Flutter
5 | - FlutterMacOS
6 | - url_launcher_ios (0.0.1):
7 | - Flutter
8 |
9 | DEPENDENCIES:
10 | - Flutter (from `Flutter`)
11 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
12 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
13 |
14 | EXTERNAL SOURCES:
15 | Flutter:
16 | :path: Flutter
17 | shared_preferences_foundation:
18 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
19 | url_launcher_ios:
20 | :path: ".symlinks/plugins/url_launcher_ios/ios"
21 |
22 | SPEC CHECKSUMS:
23 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
24 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
25 | url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
26 |
27 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
28 |
29 | COCOAPODS: 1.16.2
30 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
17 | B90E7DED794FF7F9F036D6CD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6F3078042B688C9E6B0D896 /* Pods_RunnerTests.framework */; };
18 | C7D116C2D8C4051B1107D348 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEFCD08173BEF356ABE419F6 /* Pods_Runner.framework */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXContainerItemProxy section */
22 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
23 | isa = PBXContainerItemProxy;
24 | containerPortal = 97C146E61CF9000F007C117D /* Project object */;
25 | proxyType = 1;
26 | remoteGlobalIDString = 97C146ED1CF9000F007C117D;
27 | remoteInfo = Runner;
28 | };
29 | /* End PBXContainerItemProxy section */
30 |
31 | /* Begin PBXCopyFilesBuildPhase section */
32 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
33 | isa = PBXCopyFilesBuildPhase;
34 | buildActionMask = 2147483647;
35 | dstPath = "";
36 | dstSubfolderSpec = 10;
37 | files = (
38 | );
39 | name = "Embed Frameworks";
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXCopyFilesBuildPhase section */
43 |
44 | /* Begin PBXFileReference section */
45 | 02493A2BC92F3585484F50BA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
46 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
47 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
48 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
49 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
51 | 3C74039FB71E2FC7D5582A30 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
52 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
53 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
54 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
55 | 80DA25B8D5EF10FBF52BBDBF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
56 | 8A46D74D5E1E06657EDF08B1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
57 | 8B9E5C7FB3F2990B969015C4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
58 | 8CBDCF01D91F704ED82896E0 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
59 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
60 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
61 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
62 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
63 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
64 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
65 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | C6F3078042B688C9E6B0D896 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
67 | DEFCD08173BEF356ABE419F6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
68 | /* End PBXFileReference section */
69 |
70 | /* Begin PBXFrameworksBuildPhase section */
71 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
72 | isa = PBXFrameworksBuildPhase;
73 | buildActionMask = 2147483647;
74 | files = (
75 | C7D116C2D8C4051B1107D348 /* Pods_Runner.framework in Frameworks */,
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | F8E6CA891AE778511EBEB33A /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | B90E7DED794FF7F9F036D6CD /* Pods_RunnerTests.framework in Frameworks */,
84 | );
85 | runOnlyForDeploymentPostprocessing = 0;
86 | };
87 | /* End PBXFrameworksBuildPhase section */
88 |
89 | /* Begin PBXGroup section */
90 | 331C8082294A63A400263BE5 /* RunnerTests */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 331C807B294A618700263BE5 /* RunnerTests.swift */,
94 | );
95 | path = RunnerTests;
96 | sourceTree = "";
97 | };
98 | 41C9ED40E44E760B87C3A9CD /* Frameworks */ = {
99 | isa = PBXGroup;
100 | children = (
101 | DEFCD08173BEF356ABE419F6 /* Pods_Runner.framework */,
102 | C6F3078042B688C9E6B0D896 /* Pods_RunnerTests.framework */,
103 | );
104 | name = Frameworks;
105 | sourceTree = "";
106 | };
107 | 9740EEB11CF90186004384FC /* Flutter */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
111 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
112 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
113 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
114 | );
115 | name = Flutter;
116 | sourceTree = "";
117 | };
118 | 97C146E51CF9000F007C117D = {
119 | isa = PBXGroup;
120 | children = (
121 | 9740EEB11CF90186004384FC /* Flutter */,
122 | 97C146F01CF9000F007C117D /* Runner */,
123 | 97C146EF1CF9000F007C117D /* Products */,
124 | 331C8082294A63A400263BE5 /* RunnerTests */,
125 | 9DA0C97A368B172A051CF4D7 /* Pods */,
126 | 41C9ED40E44E760B87C3A9CD /* Frameworks */,
127 | );
128 | sourceTree = "";
129 | };
130 | 97C146EF1CF9000F007C117D /* Products */ = {
131 | isa = PBXGroup;
132 | children = (
133 | 97C146EE1CF9000F007C117D /* Runner.app */,
134 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
135 | );
136 | name = Products;
137 | sourceTree = "";
138 | };
139 | 97C146F01CF9000F007C117D /* Runner */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
143 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
144 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
145 | 97C147021CF9000F007C117D /* Info.plist */,
146 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
147 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
148 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
149 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
150 | );
151 | path = Runner;
152 | sourceTree = "";
153 | };
154 | 9DA0C97A368B172A051CF4D7 /* Pods */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 8B9E5C7FB3F2990B969015C4 /* Pods-Runner.debug.xcconfig */,
158 | 80DA25B8D5EF10FBF52BBDBF /* Pods-Runner.release.xcconfig */,
159 | 02493A2BC92F3585484F50BA /* Pods-Runner.profile.xcconfig */,
160 | 8A46D74D5E1E06657EDF08B1 /* Pods-RunnerTests.debug.xcconfig */,
161 | 8CBDCF01D91F704ED82896E0 /* Pods-RunnerTests.release.xcconfig */,
162 | 3C74039FB71E2FC7D5582A30 /* Pods-RunnerTests.profile.xcconfig */,
163 | );
164 | name = Pods;
165 | path = Pods;
166 | sourceTree = "";
167 | };
168 | /* End PBXGroup section */
169 |
170 | /* Begin PBXNativeTarget section */
171 | 331C8080294A63A400263BE5 /* RunnerTests */ = {
172 | isa = PBXNativeTarget;
173 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
174 | buildPhases = (
175 | F51E0A91E6539EC064B2DDE2 /* [CP] Check Pods Manifest.lock */,
176 | 331C807D294A63A400263BE5 /* Sources */,
177 | 331C807F294A63A400263BE5 /* Resources */,
178 | F8E6CA891AE778511EBEB33A /* Frameworks */,
179 | );
180 | buildRules = (
181 | );
182 | dependencies = (
183 | 331C8086294A63A400263BE5 /* PBXTargetDependency */,
184 | );
185 | name = RunnerTests;
186 | productName = RunnerTests;
187 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
188 | productType = "com.apple.product-type.bundle.unit-test";
189 | };
190 | 97C146ED1CF9000F007C117D /* Runner */ = {
191 | isa = PBXNativeTarget;
192 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
193 | buildPhases = (
194 | AAAA648C053CC43ED99F660A /* [CP] Check Pods Manifest.lock */,
195 | 9740EEB61CF901F6004384FC /* Run Script */,
196 | 97C146EA1CF9000F007C117D /* Sources */,
197 | 97C146EB1CF9000F007C117D /* Frameworks */,
198 | 97C146EC1CF9000F007C117D /* Resources */,
199 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
200 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
201 | 6F6E1A71588F92DF4E3A2811 /* [CP] Embed Pods Frameworks */,
202 | );
203 | buildRules = (
204 | );
205 | dependencies = (
206 | );
207 | name = Runner;
208 | productName = Runner;
209 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
210 | productType = "com.apple.product-type.application";
211 | };
212 | /* End PBXNativeTarget section */
213 |
214 | /* Begin PBXProject section */
215 | 97C146E61CF9000F007C117D /* Project object */ = {
216 | isa = PBXProject;
217 | attributes = {
218 | BuildIndependentTargetsInParallel = YES;
219 | LastUpgradeCheck = 1510;
220 | ORGANIZATIONNAME = "";
221 | TargetAttributes = {
222 | 331C8080294A63A400263BE5 = {
223 | CreatedOnToolsVersion = 14.0;
224 | TestTargetID = 97C146ED1CF9000F007C117D;
225 | };
226 | 97C146ED1CF9000F007C117D = {
227 | CreatedOnToolsVersion = 7.3.1;
228 | LastSwiftMigration = 1100;
229 | };
230 | };
231 | };
232 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
233 | compatibilityVersion = "Xcode 9.3";
234 | developmentRegion = en;
235 | hasScannedForEncodings = 0;
236 | knownRegions = (
237 | en,
238 | Base,
239 | );
240 | mainGroup = 97C146E51CF9000F007C117D;
241 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
242 | projectDirPath = "";
243 | projectRoot = "";
244 | targets = (
245 | 97C146ED1CF9000F007C117D /* Runner */,
246 | 331C8080294A63A400263BE5 /* RunnerTests */,
247 | );
248 | };
249 | /* End PBXProject section */
250 |
251 | /* Begin PBXResourcesBuildPhase section */
252 | 331C807F294A63A400263BE5 /* Resources */ = {
253 | isa = PBXResourcesBuildPhase;
254 | buildActionMask = 2147483647;
255 | files = (
256 | );
257 | runOnlyForDeploymentPostprocessing = 0;
258 | };
259 | 97C146EC1CF9000F007C117D /* Resources */ = {
260 | isa = PBXResourcesBuildPhase;
261 | buildActionMask = 2147483647;
262 | files = (
263 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
264 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
265 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
266 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
267 | );
268 | runOnlyForDeploymentPostprocessing = 0;
269 | };
270 | /* End PBXResourcesBuildPhase section */
271 |
272 | /* Begin PBXShellScriptBuildPhase section */
273 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
274 | isa = PBXShellScriptBuildPhase;
275 | alwaysOutOfDate = 1;
276 | buildActionMask = 2147483647;
277 | files = (
278 | );
279 | inputPaths = (
280 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
281 | );
282 | name = "Thin Binary";
283 | outputPaths = (
284 | );
285 | runOnlyForDeploymentPostprocessing = 0;
286 | shellPath = /bin/sh;
287 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
288 | };
289 | 6F6E1A71588F92DF4E3A2811 /* [CP] Embed Pods Frameworks */ = {
290 | isa = PBXShellScriptBuildPhase;
291 | buildActionMask = 2147483647;
292 | files = (
293 | );
294 | inputFileListPaths = (
295 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
296 | );
297 | name = "[CP] Embed Pods Frameworks";
298 | outputFileListPaths = (
299 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
300 | );
301 | runOnlyForDeploymentPostprocessing = 0;
302 | shellPath = /bin/sh;
303 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
304 | showEnvVarsInLog = 0;
305 | };
306 | 9740EEB61CF901F6004384FC /* Run Script */ = {
307 | isa = PBXShellScriptBuildPhase;
308 | alwaysOutOfDate = 1;
309 | buildActionMask = 2147483647;
310 | files = (
311 | );
312 | inputPaths = (
313 | );
314 | name = "Run Script";
315 | outputPaths = (
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | shellPath = /bin/sh;
319 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
320 | };
321 | AAAA648C053CC43ED99F660A /* [CP] Check Pods Manifest.lock */ = {
322 | isa = PBXShellScriptBuildPhase;
323 | buildActionMask = 2147483647;
324 | files = (
325 | );
326 | inputFileListPaths = (
327 | );
328 | inputPaths = (
329 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
330 | "${PODS_ROOT}/Manifest.lock",
331 | );
332 | name = "[CP] Check Pods Manifest.lock";
333 | outputFileListPaths = (
334 | );
335 | outputPaths = (
336 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | shellPath = /bin/sh;
340 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
341 | showEnvVarsInLog = 0;
342 | };
343 | F51E0A91E6539EC064B2DDE2 /* [CP] Check Pods Manifest.lock */ = {
344 | isa = PBXShellScriptBuildPhase;
345 | buildActionMask = 2147483647;
346 | files = (
347 | );
348 | inputFileListPaths = (
349 | );
350 | inputPaths = (
351 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
352 | "${PODS_ROOT}/Manifest.lock",
353 | );
354 | name = "[CP] Check Pods Manifest.lock";
355 | outputFileListPaths = (
356 | );
357 | outputPaths = (
358 | "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
359 | );
360 | runOnlyForDeploymentPostprocessing = 0;
361 | shellPath = /bin/sh;
362 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
363 | showEnvVarsInLog = 0;
364 | };
365 | /* End PBXShellScriptBuildPhase section */
366 |
367 | /* Begin PBXSourcesBuildPhase section */
368 | 331C807D294A63A400263BE5 /* Sources */ = {
369 | isa = PBXSourcesBuildPhase;
370 | buildActionMask = 2147483647;
371 | files = (
372 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
373 | );
374 | runOnlyForDeploymentPostprocessing = 0;
375 | };
376 | 97C146EA1CF9000F007C117D /* Sources */ = {
377 | isa = PBXSourcesBuildPhase;
378 | buildActionMask = 2147483647;
379 | files = (
380 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
381 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
382 | );
383 | runOnlyForDeploymentPostprocessing = 0;
384 | };
385 | /* End PBXSourcesBuildPhase section */
386 |
387 | /* Begin PBXTargetDependency section */
388 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
389 | isa = PBXTargetDependency;
390 | target = 97C146ED1CF9000F007C117D /* Runner */;
391 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
392 | };
393 | /* End PBXTargetDependency section */
394 |
395 | /* Begin PBXVariantGroup section */
396 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
397 | isa = PBXVariantGroup;
398 | children = (
399 | 97C146FB1CF9000F007C117D /* Base */,
400 | );
401 | name = Main.storyboard;
402 | sourceTree = "";
403 | };
404 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
405 | isa = PBXVariantGroup;
406 | children = (
407 | 97C147001CF9000F007C117D /* Base */,
408 | );
409 | name = LaunchScreen.storyboard;
410 | sourceTree = "";
411 | };
412 | /* End PBXVariantGroup section */
413 |
414 | /* Begin XCBuildConfiguration section */
415 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
416 | isa = XCBuildConfiguration;
417 | buildSettings = {
418 | ALWAYS_SEARCH_USER_PATHS = NO;
419 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
420 | CLANG_ANALYZER_NONNULL = YES;
421 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
422 | CLANG_CXX_LIBRARY = "libc++";
423 | CLANG_ENABLE_MODULES = YES;
424 | CLANG_ENABLE_OBJC_ARC = YES;
425 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
426 | CLANG_WARN_BOOL_CONVERSION = YES;
427 | CLANG_WARN_COMMA = YES;
428 | CLANG_WARN_CONSTANT_CONVERSION = YES;
429 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
430 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
431 | CLANG_WARN_EMPTY_BODY = YES;
432 | CLANG_WARN_ENUM_CONVERSION = YES;
433 | CLANG_WARN_INFINITE_RECURSION = YES;
434 | CLANG_WARN_INT_CONVERSION = YES;
435 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
436 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
437 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
438 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
439 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
440 | CLANG_WARN_STRICT_PROTOTYPES = YES;
441 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
442 | CLANG_WARN_UNREACHABLE_CODE = YES;
443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
444 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
445 | COPY_PHASE_STRIP = NO;
446 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
447 | ENABLE_NS_ASSERTIONS = NO;
448 | ENABLE_STRICT_OBJC_MSGSEND = YES;
449 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
450 | GCC_C_LANGUAGE_STANDARD = gnu99;
451 | GCC_NO_COMMON_BLOCKS = YES;
452 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
453 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
454 | GCC_WARN_UNDECLARED_SELECTOR = YES;
455 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
456 | GCC_WARN_UNUSED_FUNCTION = YES;
457 | GCC_WARN_UNUSED_VARIABLE = YES;
458 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
459 | MTL_ENABLE_DEBUG_INFO = NO;
460 | SDKROOT = iphoneos;
461 | SUPPORTED_PLATFORMS = iphoneos;
462 | TARGETED_DEVICE_FAMILY = "1,2";
463 | VALIDATE_PRODUCT = YES;
464 | };
465 | name = Profile;
466 | };
467 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
468 | isa = XCBuildConfiguration;
469 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
470 | buildSettings = {
471 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
472 | CLANG_ENABLE_MODULES = YES;
473 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
474 | ENABLE_BITCODE = NO;
475 | INFOPLIST_FILE = Runner/Info.plist;
476 | LD_RUNPATH_SEARCH_PATHS = (
477 | "$(inherited)",
478 | "@executable_path/Frameworks",
479 | );
480 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
481 | PRODUCT_NAME = "$(TARGET_NAME)";
482 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
483 | SWIFT_VERSION = 5.0;
484 | VERSIONING_SYSTEM = "apple-generic";
485 | };
486 | name = Profile;
487 | };
488 | 331C8088294A63A400263BE5 /* Debug */ = {
489 | isa = XCBuildConfiguration;
490 | baseConfigurationReference = 8A46D74D5E1E06657EDF08B1 /* Pods-RunnerTests.debug.xcconfig */;
491 | buildSettings = {
492 | BUNDLE_LOADER = "$(TEST_HOST)";
493 | CODE_SIGN_STYLE = Automatic;
494 | CURRENT_PROJECT_VERSION = 1;
495 | GENERATE_INFOPLIST_FILE = YES;
496 | MARKETING_VERSION = 1.0;
497 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests;
498 | PRODUCT_NAME = "$(TARGET_NAME)";
499 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
500 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
501 | SWIFT_VERSION = 5.0;
502 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
503 | };
504 | name = Debug;
505 | };
506 | 331C8089294A63A400263BE5 /* Release */ = {
507 | isa = XCBuildConfiguration;
508 | baseConfigurationReference = 8CBDCF01D91F704ED82896E0 /* Pods-RunnerTests.release.xcconfig */;
509 | buildSettings = {
510 | BUNDLE_LOADER = "$(TEST_HOST)";
511 | CODE_SIGN_STYLE = Automatic;
512 | CURRENT_PROJECT_VERSION = 1;
513 | GENERATE_INFOPLIST_FILE = YES;
514 | MARKETING_VERSION = 1.0;
515 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests;
516 | PRODUCT_NAME = "$(TARGET_NAME)";
517 | SWIFT_VERSION = 5.0;
518 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
519 | };
520 | name = Release;
521 | };
522 | 331C808A294A63A400263BE5 /* Profile */ = {
523 | isa = XCBuildConfiguration;
524 | baseConfigurationReference = 3C74039FB71E2FC7D5582A30 /* Pods-RunnerTests.profile.xcconfig */;
525 | buildSettings = {
526 | BUNDLE_LOADER = "$(TEST_HOST)";
527 | CODE_SIGN_STYLE = Automatic;
528 | CURRENT_PROJECT_VERSION = 1;
529 | GENERATE_INFOPLIST_FILE = YES;
530 | MARKETING_VERSION = 1.0;
531 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests;
532 | PRODUCT_NAME = "$(TARGET_NAME)";
533 | SWIFT_VERSION = 5.0;
534 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
535 | };
536 | name = Profile;
537 | };
538 | 97C147031CF9000F007C117D /* Debug */ = {
539 | isa = XCBuildConfiguration;
540 | buildSettings = {
541 | ALWAYS_SEARCH_USER_PATHS = NO;
542 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
543 | CLANG_ANALYZER_NONNULL = YES;
544 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
545 | CLANG_CXX_LIBRARY = "libc++";
546 | CLANG_ENABLE_MODULES = YES;
547 | CLANG_ENABLE_OBJC_ARC = YES;
548 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
549 | CLANG_WARN_BOOL_CONVERSION = YES;
550 | CLANG_WARN_COMMA = YES;
551 | CLANG_WARN_CONSTANT_CONVERSION = YES;
552 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
553 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
554 | CLANG_WARN_EMPTY_BODY = YES;
555 | CLANG_WARN_ENUM_CONVERSION = YES;
556 | CLANG_WARN_INFINITE_RECURSION = YES;
557 | CLANG_WARN_INT_CONVERSION = YES;
558 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
559 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
560 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
561 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
562 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
563 | CLANG_WARN_STRICT_PROTOTYPES = YES;
564 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
565 | CLANG_WARN_UNREACHABLE_CODE = YES;
566 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
567 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
568 | COPY_PHASE_STRIP = NO;
569 | DEBUG_INFORMATION_FORMAT = dwarf;
570 | ENABLE_STRICT_OBJC_MSGSEND = YES;
571 | ENABLE_TESTABILITY = YES;
572 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
573 | GCC_C_LANGUAGE_STANDARD = gnu99;
574 | GCC_DYNAMIC_NO_PIC = NO;
575 | GCC_NO_COMMON_BLOCKS = YES;
576 | GCC_OPTIMIZATION_LEVEL = 0;
577 | GCC_PREPROCESSOR_DEFINITIONS = (
578 | "DEBUG=1",
579 | "$(inherited)",
580 | );
581 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
582 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
583 | GCC_WARN_UNDECLARED_SELECTOR = YES;
584 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
585 | GCC_WARN_UNUSED_FUNCTION = YES;
586 | GCC_WARN_UNUSED_VARIABLE = YES;
587 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
588 | MTL_ENABLE_DEBUG_INFO = YES;
589 | ONLY_ACTIVE_ARCH = YES;
590 | SDKROOT = iphoneos;
591 | TARGETED_DEVICE_FAMILY = "1,2";
592 | };
593 | name = Debug;
594 | };
595 | 97C147041CF9000F007C117D /* Release */ = {
596 | isa = XCBuildConfiguration;
597 | buildSettings = {
598 | ALWAYS_SEARCH_USER_PATHS = NO;
599 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
600 | CLANG_ANALYZER_NONNULL = YES;
601 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
602 | CLANG_CXX_LIBRARY = "libc++";
603 | CLANG_ENABLE_MODULES = YES;
604 | CLANG_ENABLE_OBJC_ARC = YES;
605 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
606 | CLANG_WARN_BOOL_CONVERSION = YES;
607 | CLANG_WARN_COMMA = YES;
608 | CLANG_WARN_CONSTANT_CONVERSION = YES;
609 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
610 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
611 | CLANG_WARN_EMPTY_BODY = YES;
612 | CLANG_WARN_ENUM_CONVERSION = YES;
613 | CLANG_WARN_INFINITE_RECURSION = YES;
614 | CLANG_WARN_INT_CONVERSION = YES;
615 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
616 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
617 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
618 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
619 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
620 | CLANG_WARN_STRICT_PROTOTYPES = YES;
621 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
622 | CLANG_WARN_UNREACHABLE_CODE = YES;
623 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
624 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
625 | COPY_PHASE_STRIP = NO;
626 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
627 | ENABLE_NS_ASSERTIONS = NO;
628 | ENABLE_STRICT_OBJC_MSGSEND = YES;
629 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
630 | GCC_C_LANGUAGE_STANDARD = gnu99;
631 | GCC_NO_COMMON_BLOCKS = YES;
632 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
633 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
634 | GCC_WARN_UNDECLARED_SELECTOR = YES;
635 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
636 | GCC_WARN_UNUSED_FUNCTION = YES;
637 | GCC_WARN_UNUSED_VARIABLE = YES;
638 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
639 | MTL_ENABLE_DEBUG_INFO = NO;
640 | SDKROOT = iphoneos;
641 | SUPPORTED_PLATFORMS = iphoneos;
642 | SWIFT_COMPILATION_MODE = wholemodule;
643 | SWIFT_OPTIMIZATION_LEVEL = "-O";
644 | TARGETED_DEVICE_FAMILY = "1,2";
645 | VALIDATE_PRODUCT = YES;
646 | };
647 | name = Release;
648 | };
649 | 97C147061CF9000F007C117D /* Debug */ = {
650 | isa = XCBuildConfiguration;
651 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
652 | buildSettings = {
653 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
654 | CLANG_ENABLE_MODULES = YES;
655 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
656 | ENABLE_BITCODE = NO;
657 | INFOPLIST_FILE = Runner/Info.plist;
658 | LD_RUNPATH_SEARCH_PATHS = (
659 | "$(inherited)",
660 | "@executable_path/Frameworks",
661 | );
662 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
663 | PRODUCT_NAME = "$(TARGET_NAME)";
664 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
665 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
666 | SWIFT_VERSION = 5.0;
667 | VERSIONING_SYSTEM = "apple-generic";
668 | };
669 | name = Debug;
670 | };
671 | 97C147071CF9000F007C117D /* Release */ = {
672 | isa = XCBuildConfiguration;
673 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
674 | buildSettings = {
675 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
676 | CLANG_ENABLE_MODULES = YES;
677 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
678 | ENABLE_BITCODE = NO;
679 | INFOPLIST_FILE = Runner/Info.plist;
680 | LD_RUNPATH_SEARCH_PATHS = (
681 | "$(inherited)",
682 | "@executable_path/Frameworks",
683 | );
684 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
685 | PRODUCT_NAME = "$(TARGET_NAME)";
686 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
687 | SWIFT_VERSION = 5.0;
688 | VERSIONING_SYSTEM = "apple-generic";
689 | };
690 | name = Release;
691 | };
692 | /* End XCBuildConfiguration section */
693 |
694 | /* Begin XCConfigurationList section */
695 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
696 | isa = XCConfigurationList;
697 | buildConfigurations = (
698 | 331C8088294A63A400263BE5 /* Debug */,
699 | 331C8089294A63A400263BE5 /* Release */,
700 | 331C808A294A63A400263BE5 /* Profile */,
701 | );
702 | defaultConfigurationIsVisible = 0;
703 | defaultConfigurationName = Release;
704 | };
705 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
706 | isa = XCConfigurationList;
707 | buildConfigurations = (
708 | 97C147031CF9000F007C117D /* Debug */,
709 | 97C147041CF9000F007C117D /* Release */,
710 | 249021D3217E4FDB00AE95B9 /* Profile */,
711 | );
712 | defaultConfigurationIsVisible = 0;
713 | defaultConfigurationName = Release;
714 | };
715 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
716 | isa = XCConfigurationList;
717 | buildConfigurations = (
718 | 97C147061CF9000F007C117D /* Debug */,
719 | 97C147071CF9000F007C117D /* Release */,
720 | 249021D4217E4FDB00AE95B9 /* Profile */,
721 | );
722 | defaultConfigurationIsVisible = 0;
723 | defaultConfigurationName = Release;
724 | };
725 | /* End XCConfigurationList section */
726 | };
727 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
728 | }
729 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
64 |
66 |
72 |
73 |
74 |
75 |
81 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @main
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 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/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 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UserOrient/userorient-flutter/35374c0ac1b2f36bd651527cf942e5bba5a3b155/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/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.
--------------------------------------------------------------------------------
/example/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 |
--------------------------------------------------------------------------------
/example/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 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | example
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:userorient_flutter/userorient_flutter.dart';
3 |
4 | void main() async {
5 | WidgetsFlutterBinding.ensureInitialized();
6 |
7 | UserOrient.configure(
8 | apiKey: 'YOUR_API_KEY',
9 | languageCode: 'en',
10 | );
11 |
12 | runApp(const MainApp());
13 | }
14 |
15 | class MainApp extends StatelessWidget {
16 | const MainApp({super.key});
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return const MaterialApp(
21 | debugShowCheckedModeBanner: false,
22 | home: HomePage(),
23 | );
24 | }
25 | }
26 |
27 | class HomePage extends StatelessWidget {
28 | const HomePage({super.key});
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Scaffold(
33 | body: SizedBox(
34 | width: double.infinity,
35 | child: Column(
36 | mainAxisAlignment: MainAxisAlignment.center,
37 | children: [
38 | ListTile(
39 | leading: const Icon(Icons.feedback),
40 | title: const Text(
41 | 'Feature requests',
42 | ),
43 | subtitle: const Text(
44 | 'View and vote on feature requests',
45 | ),
46 | onTap: () {
47 | UserOrient.setUser(
48 | // uniqueIdentifier: '123123',
49 | // fullName: 'Kamran',
50 | // phoneNumber: '+994501234567',
51 | // email: 'kamran@userorient.com',
52 | // language: 'az',
53 | // extra: {
54 | // 'is_premium': true,
55 | // 'is_azerbaijani': true,
56 | // 'online_session_count': 17,
57 | // 'subscription_date': '2021-09-01',
58 | // },
59 | );
60 |
61 | UserOrient.openBoard(context);
62 | },
63 | ),
64 | ListTile(
65 | leading: const Icon(Icons.feedback),
66 | title: const Text(
67 | 'Logout',
68 | ),
69 | onTap: () {
70 | UserOrient.clearCache();
71 | },
72 | ),
73 | ],
74 | ),
75 | ),
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | args:
5 | dependency: transitive
6 | description:
7 | name: args
8 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.6.0"
12 | async:
13 | dependency: transitive
14 | description:
15 | name: async
16 | sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.13.0"
20 | boolean_selector:
21 | dependency: transitive
22 | description:
23 | name: boolean_selector
24 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.1.2"
28 | characters:
29 | dependency: transitive
30 | description:
31 | name: characters
32 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.4.0"
36 | clock:
37 | dependency: transitive
38 | description:
39 | name: clock
40 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.1.2"
44 | collection:
45 | dependency: transitive
46 | description:
47 | name: collection
48 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.19.1"
52 | fake_async:
53 | dependency: transitive
54 | description:
55 | name: fake_async
56 | sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.3.3"
60 | ffi:
61 | dependency: transitive
62 | description:
63 | name: ffi
64 | sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "2.1.4"
68 | file:
69 | dependency: transitive
70 | description:
71 | name: file
72 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "7.0.1"
76 | flutter:
77 | dependency: "direct main"
78 | description: flutter
79 | source: sdk
80 | version: "0.0.0"
81 | flutter_lints:
82 | dependency: "direct dev"
83 | description:
84 | name: flutter_lints
85 | sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
86 | url: "https://pub.dev"
87 | source: hosted
88 | version: "3.0.2"
89 | flutter_svg:
90 | dependency: transitive
91 | description:
92 | name: flutter_svg
93 | sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
94 | url: "https://pub.dev"
95 | source: hosted
96 | version: "2.0.17"
97 | flutter_svg_provider:
98 | dependency: transitive
99 | description:
100 | name: flutter_svg_provider
101 | sha256: cda47ab350671ba51ae4605d48f4c82fa5a2c399d22ebda367c1b407234c5048
102 | url: "https://pub.dev"
103 | source: hosted
104 | version: "1.0.7"
105 | flutter_test:
106 | dependency: "direct dev"
107 | description: flutter
108 | source: sdk
109 | version: "0.0.0"
110 | flutter_web_plugins:
111 | dependency: transitive
112 | description: flutter
113 | source: sdk
114 | version: "0.0.0"
115 | http:
116 | dependency: transitive
117 | description:
118 | name: http
119 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
120 | url: "https://pub.dev"
121 | source: hosted
122 | version: "1.3.0"
123 | http_parser:
124 | dependency: transitive
125 | description:
126 | name: http_parser
127 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
128 | url: "https://pub.dev"
129 | source: hosted
130 | version: "4.1.2"
131 | leak_tracker:
132 | dependency: transitive
133 | description:
134 | name: leak_tracker
135 | sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
136 | url: "https://pub.dev"
137 | source: hosted
138 | version: "10.0.9"
139 | leak_tracker_flutter_testing:
140 | dependency: transitive
141 | description:
142 | name: leak_tracker_flutter_testing
143 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
144 | url: "https://pub.dev"
145 | source: hosted
146 | version: "3.0.9"
147 | leak_tracker_testing:
148 | dependency: transitive
149 | description:
150 | name: leak_tracker_testing
151 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
152 | url: "https://pub.dev"
153 | source: hosted
154 | version: "3.0.1"
155 | lints:
156 | dependency: transitive
157 | description:
158 | name: lints
159 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
160 | url: "https://pub.dev"
161 | source: hosted
162 | version: "3.0.0"
163 | matcher:
164 | dependency: transitive
165 | description:
166 | name: matcher
167 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
168 | url: "https://pub.dev"
169 | source: hosted
170 | version: "0.12.17"
171 | material_color_utilities:
172 | dependency: transitive
173 | description:
174 | name: material_color_utilities
175 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
176 | url: "https://pub.dev"
177 | source: hosted
178 | version: "0.11.1"
179 | meta:
180 | dependency: transitive
181 | description:
182 | name: meta
183 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
184 | url: "https://pub.dev"
185 | source: hosted
186 | version: "1.16.0"
187 | path:
188 | dependency: transitive
189 | description:
190 | name: path
191 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
192 | url: "https://pub.dev"
193 | source: hosted
194 | version: "1.9.1"
195 | path_parsing:
196 | dependency: transitive
197 | description:
198 | name: path_parsing
199 | sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
200 | url: "https://pub.dev"
201 | source: hosted
202 | version: "1.1.0"
203 | path_provider_linux:
204 | dependency: transitive
205 | description:
206 | name: path_provider_linux
207 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
208 | url: "https://pub.dev"
209 | source: hosted
210 | version: "2.2.1"
211 | path_provider_platform_interface:
212 | dependency: transitive
213 | description:
214 | name: path_provider_platform_interface
215 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
216 | url: "https://pub.dev"
217 | source: hosted
218 | version: "2.1.2"
219 | path_provider_windows:
220 | dependency: transitive
221 | description:
222 | name: path_provider_windows
223 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
224 | url: "https://pub.dev"
225 | source: hosted
226 | version: "2.3.0"
227 | petitparser:
228 | dependency: transitive
229 | description:
230 | name: petitparser
231 | sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
232 | url: "https://pub.dev"
233 | source: hosted
234 | version: "6.1.0"
235 | platform:
236 | dependency: transitive
237 | description:
238 | name: platform
239 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
240 | url: "https://pub.dev"
241 | source: hosted
242 | version: "3.1.6"
243 | plugin_platform_interface:
244 | dependency: transitive
245 | description:
246 | name: plugin_platform_interface
247 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
248 | url: "https://pub.dev"
249 | source: hosted
250 | version: "2.1.8"
251 | shared_preferences:
252 | dependency: transitive
253 | description:
254 | name: shared_preferences
255 | sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a"
256 | url: "https://pub.dev"
257 | source: hosted
258 | version: "2.5.2"
259 | shared_preferences_android:
260 | dependency: transitive
261 | description:
262 | name: shared_preferences_android
263 | sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22
264 | url: "https://pub.dev"
265 | source: hosted
266 | version: "2.4.6"
267 | shared_preferences_foundation:
268 | dependency: transitive
269 | description:
270 | name: shared_preferences_foundation
271 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
272 | url: "https://pub.dev"
273 | source: hosted
274 | version: "2.5.4"
275 | shared_preferences_linux:
276 | dependency: transitive
277 | description:
278 | name: shared_preferences_linux
279 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
280 | url: "https://pub.dev"
281 | source: hosted
282 | version: "2.4.1"
283 | shared_preferences_platform_interface:
284 | dependency: transitive
285 | description:
286 | name: shared_preferences_platform_interface
287 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
288 | url: "https://pub.dev"
289 | source: hosted
290 | version: "2.4.1"
291 | shared_preferences_web:
292 | dependency: transitive
293 | description:
294 | name: shared_preferences_web
295 | sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
296 | url: "https://pub.dev"
297 | source: hosted
298 | version: "2.4.3"
299 | shared_preferences_windows:
300 | dependency: transitive
301 | description:
302 | name: shared_preferences_windows
303 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
304 | url: "https://pub.dev"
305 | source: hosted
306 | version: "2.4.1"
307 | sky_engine:
308 | dependency: transitive
309 | description: flutter
310 | source: sdk
311 | version: "0.0.0"
312 | source_span:
313 | dependency: transitive
314 | description:
315 | name: source_span
316 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
317 | url: "https://pub.dev"
318 | source: hosted
319 | version: "1.10.1"
320 | stack_trace:
321 | dependency: transitive
322 | description:
323 | name: stack_trace
324 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
325 | url: "https://pub.dev"
326 | source: hosted
327 | version: "1.12.1"
328 | stream_channel:
329 | dependency: transitive
330 | description:
331 | name: stream_channel
332 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
333 | url: "https://pub.dev"
334 | source: hosted
335 | version: "2.1.4"
336 | string_scanner:
337 | dependency: transitive
338 | description:
339 | name: string_scanner
340 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
341 | url: "https://pub.dev"
342 | source: hosted
343 | version: "1.4.1"
344 | term_glyph:
345 | dependency: transitive
346 | description:
347 | name: term_glyph
348 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
349 | url: "https://pub.dev"
350 | source: hosted
351 | version: "1.2.2"
352 | test_api:
353 | dependency: transitive
354 | description:
355 | name: test_api
356 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
357 | url: "https://pub.dev"
358 | source: hosted
359 | version: "0.7.4"
360 | typed_data:
361 | dependency: transitive
362 | description:
363 | name: typed_data
364 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
365 | url: "https://pub.dev"
366 | source: hosted
367 | version: "1.4.0"
368 | url_launcher:
369 | dependency: transitive
370 | description:
371 | name: url_launcher
372 | sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
373 | url: "https://pub.dev"
374 | source: hosted
375 | version: "6.3.1"
376 | url_launcher_android:
377 | dependency: transitive
378 | description:
379 | name: url_launcher_android
380 | sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
381 | url: "https://pub.dev"
382 | source: hosted
383 | version: "6.3.14"
384 | url_launcher_ios:
385 | dependency: transitive
386 | description:
387 | name: url_launcher_ios
388 | sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
389 | url: "https://pub.dev"
390 | source: hosted
391 | version: "6.3.2"
392 | url_launcher_linux:
393 | dependency: transitive
394 | description:
395 | name: url_launcher_linux
396 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
397 | url: "https://pub.dev"
398 | source: hosted
399 | version: "3.2.1"
400 | url_launcher_macos:
401 | dependency: transitive
402 | description:
403 | name: url_launcher_macos
404 | sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
405 | url: "https://pub.dev"
406 | source: hosted
407 | version: "3.2.2"
408 | url_launcher_platform_interface:
409 | dependency: transitive
410 | description:
411 | name: url_launcher_platform_interface
412 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
413 | url: "https://pub.dev"
414 | source: hosted
415 | version: "2.3.2"
416 | url_launcher_web:
417 | dependency: transitive
418 | description:
419 | name: url_launcher_web
420 | sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
421 | url: "https://pub.dev"
422 | source: hosted
423 | version: "2.4.0"
424 | url_launcher_windows:
425 | dependency: transitive
426 | description:
427 | name: url_launcher_windows
428 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
429 | url: "https://pub.dev"
430 | source: hosted
431 | version: "3.1.4"
432 | userorient_flutter:
433 | dependency: "direct main"
434 | description:
435 | path: ".."
436 | relative: true
437 | source: path
438 | version: "1.1.0"
439 | vector_graphics:
440 | dependency: transitive
441 | description:
442 | name: vector_graphics
443 | sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
444 | url: "https://pub.dev"
445 | source: hosted
446 | version: "1.1.18"
447 | vector_graphics_codec:
448 | dependency: transitive
449 | description:
450 | name: vector_graphics_codec
451 | sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
452 | url: "https://pub.dev"
453 | source: hosted
454 | version: "1.1.13"
455 | vector_graphics_compiler:
456 | dependency: transitive
457 | description:
458 | name: vector_graphics_compiler
459 | sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
460 | url: "https://pub.dev"
461 | source: hosted
462 | version: "1.1.16"
463 | vector_math:
464 | dependency: transitive
465 | description:
466 | name: vector_math
467 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
468 | url: "https://pub.dev"
469 | source: hosted
470 | version: "2.1.4"
471 | vm_service:
472 | dependency: transitive
473 | description:
474 | name: vm_service
475 | sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
476 | url: "https://pub.dev"
477 | source: hosted
478 | version: "15.0.0"
479 | web:
480 | dependency: transitive
481 | description:
482 | name: web
483 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
484 | url: "https://pub.dev"
485 | source: hosted
486 | version: "1.1.0"
487 | xdg_directories:
488 | dependency: transitive
489 | description:
490 | name: xdg_directories
491 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
492 | url: "https://pub.dev"
493 | source: hosted
494 | version: "1.1.0"
495 | xml:
496 | dependency: transitive
497 | description:
498 | name: xml
499 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
500 | url: "https://pub.dev"
501 | source: hosted
502 | version: "6.5.0"
503 | sdks:
504 | dart: ">=3.7.0 <4.0.0"
505 | flutter: ">=3.27.0"
506 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: "A new Flutter project."
3 | publish_to: 'none'
4 | version: 1.0.0
5 |
6 | environment:
7 | sdk: '>=3.3.3 <4.0.0'
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 | userorient_flutter:
13 | path: ../
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 | flutter_lints: ^3.0.0
19 |
20 | flutter:
21 | uses-material-design: true
22 |
--------------------------------------------------------------------------------
/lib/src/logic/l10n.dart:
--------------------------------------------------------------------------------
1 | import 'package:userorient_flutter/userorient_flutter.dart';
2 |
3 | class L10n {
4 | static bool isSupportedLanguage(String languageCode) {
5 | return _content.containsKey(languageCode);
6 | }
7 |
8 | static const Map _content = {
9 | 'az': {
10 | 'title': 'Təkliflər',
11 | 'tip': 'Tezliklə görmək istədiklərinizə səs verin.',
12 | 'form_hint': 'Təklifiniz nədir?',
13 | 'submit_form': 'Göndər',
14 | 'sent_title': 'Təklif göndərildi!',
15 | 'sent_description':
16 | 'Təklifinizi nəzərdən keçirəcəyik və əgər bizim yol xəritəmizə uyğun gələrsə, onu siyahıya əlavə edəcəyik. Gözləmədə qalın!',
17 | 'go_back': 'Geri qayıt',
18 | 'add_feature': 'Təklif Göndər',
19 | 'roadmap': 'Yol Xəritəsi',
20 | 'implemented': 'Tamamlanmış',
21 | 'formEmpty': 'Təklifinizi daxil edin',
22 | },
23 | 'en': {
24 | 'title': 'Features',
25 | 'tip': 'Vote the features you want to see soon.',
26 | 'form_hint': 'Describe your idea...',
27 | 'submit_form': 'Submit',
28 | 'sent_title': 'Feature request sent!',
29 | 'sent_description':
30 | 'We will review your request and if it fits our roadmap, we will add it to our list of features to build. Stay tuned!',
31 | 'go_back': 'Go back',
32 | 'add_feature': 'Suggest Feature',
33 | 'roadmap': 'Roadmap',
34 | 'implemented': 'Implemented',
35 | 'formEmpty': 'Please enter your suggestion',
36 | },
37 | 'es': {
38 | 'title': 'Sugerencias',
39 | 'tip': 'Vota las funciones que deseas pronto.',
40 | 'form_hint': 'Describe tu idea...',
41 | 'submit_form': 'Enviar',
42 | 'sent_title': '¡Solicitud de sugerencia enviada!',
43 | 'sent_description':
44 | 'Revisaremos tu solicitud y, si encaja en nuestra hoja de ruta, la añadiremos a nuestra lista de características por desarrollar. ¡Mantente atento!',
45 | 'go_back': 'Volver',
46 | 'add_feature': 'Agregar Sugerencia',
47 | 'roadmap': 'Ruta',
48 | 'implemented': 'Implementado',
49 | 'formEmpty': 'Ingresa tu sugerencia',
50 | },
51 | 'it': {
52 | 'title': 'Suggerimenti',
53 | 'tip': 'Vota le funzionalità che vuoi vedere presto.',
54 | 'form_hint': 'Descrivi la tua idea...',
55 | 'submit_form': 'Invia',
56 | 'sent_title': 'Richiesta di funzionalità inviata!',
57 | 'sent_description':
58 | 'Esamineremo la tua richiesta e, se si adatta alla nostra roadmap, la aggiungeremo alla nostra lista di funzionalità da sviluppare. Resta sintonizzato!',
59 | 'go_back': 'Torna indietro',
60 | 'add_feature': 'Aggiungi Funzionalità',
61 | 'roadmap': 'Rotta',
62 | 'implemented': 'Implementato',
63 | 'formEmpty': 'Inserisci il tuo suggerimento',
64 | },
65 | 'tr': {
66 | 'title': 'Öneriler',
67 | 'tip': 'Yakında görmek isteklerinizi oylayın.',
68 | 'form_hint': 'Fikrinizi tanımlayın...',
69 | 'submit_form': 'Gönder',
70 | 'sent_title': 'Öneri isteği gönderildi!',
71 | 'sent_description':
72 | 'Talebinizi inceleyeceğiz ve yol haritamıza uyuyorsa, geliştirilecek özellikler listemize ekleyeceğiz. Takipte kalın!',
73 | 'go_back': 'Geri dön',
74 | 'add_feature': 'Öneri Gönder',
75 | 'roadmap': 'Yol Haritası',
76 | 'implemented': 'Tamamlanmış',
77 | 'formEmpty': 'Önerinizi girin',
78 | },
79 | };
80 |
81 | static String get _languageCode => UserOrient.languageCode;
82 |
83 | static String get tip => _content[_languageCode]!['tip'] ?? 'N/A';
84 | static String get formHint => _content[_languageCode]!['form_hint'] ?? 'N/A';
85 | static String get submitForm =>
86 | _content[_languageCode]!['submit_form'] ?? 'N/A';
87 | static String get sentTitle =>
88 | _content[_languageCode]!['sent_title'] ?? 'N/A';
89 | static String get sentDescription =>
90 | _content[_languageCode]!['sent_description'] ?? 'N/A';
91 | static String get goBack => _content[_languageCode]!['go_back'] ?? 'N/A';
92 |
93 | static String get addFeature =>
94 | _content[_languageCode]!['add_feature'] ?? 'N/A';
95 | static String get title => _content[_languageCode]!['title'] ?? 'N/A';
96 | static String get roadmap => _content[_languageCode]!['roadmap'] ?? 'N/A';
97 | static String get implemented =>
98 | _content[_languageCode]!['implemented'] ?? 'N/A';
99 | static String get formEmpty => _content[_languageCode]!['formEmpty'] ?? 'N/A';
100 | }
101 |
--------------------------------------------------------------------------------
/lib/src/logic/user_orient.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:shared_preferences/shared_preferences.dart';
4 | import 'package:userorient_flutter/src/logic/l10n.dart';
5 | import 'package:userorient_flutter/src/utilities/navigation.dart';
6 |
7 | import 'package:userorient_flutter/src/models/feature.dart';
8 | import 'package:userorient_flutter/src/logic/user_orient_data.dart';
9 | import 'package:userorient_flutter/src/models/user.dart';
10 | import 'package:userorient_flutter/src/utilities/helper_functions.dart';
11 | import 'package:userorient_flutter/src/views/board_view.dart';
12 | import 'package:userorient_flutter/src/views/form_view.dart';
13 |
14 | class UserOrient {
15 | static final ValueNotifier?> features = ValueNotifier(null);
16 |
17 | static String? _apiKey;
18 | static User? user;
19 | static UserUUID? userUuid;
20 | static bool _isInitialized = false;
21 | static String languageCode = 'en';
22 |
23 | /// Open the UserOrient board view
24 | static Future openBoard(BuildContext context) {
25 | _initialize();
26 | return Navigation.push(context, const BoardView());
27 | }
28 |
29 | /// Open the UserOrient feature request form
30 | static Future openForm(BuildContext context) {
31 | _initialize();
32 | return Navigation.push(context, const FormView());
33 | }
34 |
35 | /// Configure the UserOrient SDK. This method must be called before using the SDK.
36 | ///
37 | /// [apiKey] is the API Key from the UserOrient dashboard.
38 | /// [languageCode] is the language code for the user's language.
39 | static void configure({
40 | required String apiKey,
41 | required String languageCode,
42 | }) {
43 | /// Ignore the language code if it's not English
44 | if (L10n.isSupportedLanguage(languageCode)) {
45 | UserOrient.languageCode = languageCode.toLowerCase();
46 | }
47 |
48 | _apiKey = apiKey;
49 | }
50 |
51 | static void setUser({
52 | String? uniqueIdentifier,
53 | String? fullName,
54 | String? email,
55 | String? phoneNumber,
56 | String? language,
57 | Map? extra,
58 | }) {
59 | final User user = User(
60 | uniqueIdentifier: uniqueIdentifier,
61 | fullName: fullName,
62 | email: email,
63 | phoneNumber: phoneNumber,
64 | language: language,
65 | extra: extra,
66 | );
67 |
68 | UserOrient.user = user;
69 |
70 | logUO('Set user', emoji: '👤');
71 | }
72 |
73 | static Future clearCache() async {
74 | _isInitialized = false;
75 | userUuid = null;
76 |
77 | features.value = null;
78 |
79 | final SharedPreferences prefs = await SharedPreferences.getInstance();
80 | await prefs.remove('user_orient_project_id');
81 | await prefs.remove('user_orient_user_uuid');
82 |
83 | logUO('Cache cleared', emoji: '🆑');
84 | }
85 |
86 | static Future _initialize() async {
87 | final SharedPreferences prefs = await SharedPreferences.getInstance();
88 |
89 | final String? cachedProjectId = prefs.getString('user_orient_project_id');
90 | final bool hasProjectId = cachedProjectId != null;
91 | final bool projectChanged = hasProjectId && cachedProjectId != _apiKey;
92 |
93 | if (projectChanged) {
94 | await clearCache();
95 |
96 | logUO('Project changed, cache cleared...', emoji: '🔄');
97 | }
98 |
99 | if (!_isInitialized) {
100 | logUO('Initializing the SDK', emoji: '🚁');
101 |
102 | if (_apiKey == null) {
103 | throw 'Call `UserOrient.configure()` method before using the SDK';
104 | }
105 |
106 | // TODO: if user id is cached, continue do that in the background
107 | userUuid = await UserOrientData.resolveUserUuid(
108 | projectId: _apiKey!,
109 | user: user,
110 | );
111 |
112 | await _fetchAndSetFeatures();
113 |
114 | logUO(
115 | 'Initialization completed for project $_apiKey',
116 | emoji: '✅',
117 | );
118 |
119 | await prefs.setString('user_orient_project_id', _apiKey!);
120 |
121 | _isInitialized = true;
122 | } else {
123 | await _fetchAndSetFeatures();
124 | }
125 | }
126 |
127 | static Future _fetchAndSetFeatures() async {
128 | final List results = await Future.wait([
129 | UserOrientData.getFeatures(projectId: _apiKey!, userId: userUuid!),
130 | ]);
131 |
132 | final List features = results[0];
133 | UserOrient.features.value = features;
134 | }
135 |
136 | /// Toggle the upvote status of a feature. Used internally by the SDK.
137 | static Future toggleUpvote(Feature feature) async {
138 | final List updatedFeatures = UserOrient.features.value!.map((f) {
139 | if (f.id == feature.id) {
140 | return feature.copyWith(
141 | voted: !feature.voted,
142 | voteCount:
143 | feature.voted ? feature.voteCount - 1 : feature.voteCount + 1,
144 | );
145 | }
146 |
147 | return f;
148 | }).toList();
149 |
150 | UserOrient.features.value = updatedFeatures;
151 |
152 | await UserOrientData.toggleUpvote(
153 | projectId: _apiKey!,
154 | userId: userUuid!,
155 | feature: feature,
156 | );
157 |
158 | logUO(
159 | !feature.voted ? 'Upvoted' : 'Removed upvote',
160 | emoji: !feature.voted ? '👍' : '😶🌫️',
161 | );
162 | }
163 |
164 | /// Submit a feature request. Used internally by the SDK.
165 | static Future submitForm({
166 | required String content,
167 | }) async {
168 | logUO('Sending feature request', emoji: '📬');
169 |
170 | await UserOrientData.sendFeatureRequest(
171 | projectId: _apiKey!,
172 | userId: userUuid!,
173 | content: content,
174 | );
175 |
176 | logUO('Feature request sent', emoji: '🚀');
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/lib/src/logic/user_orient_data.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:http/http.dart' as http;
4 | import 'package:shared_preferences/shared_preferences.dart';
5 |
6 | import 'package:userorient_flutter/src/models/endpoint.dart';
7 | import 'package:userorient_flutter/src/models/feature.dart';
8 | import 'package:userorient_flutter/src/models/user.dart';
9 | import 'package:userorient_flutter/src/utilities/helper_functions.dart';
10 | import 'package:userorient_flutter/src/utilities/restful_endpoints.dart';
11 |
12 | typedef UserUUID = String;
13 |
14 | class UserOrientData {
15 | static Future syncUser({
16 | required User? user,
17 | required String? cachedId,
18 | required String projectId,
19 | }) async {
20 | user ??= const User.anonymous();
21 | final Endpoint endpoint = RestfulEndpoints.syncUser(projectId);
22 |
23 | logUO('Syncing user: ${user.toJson(cachedId)}', emoji: '🔄');
24 |
25 | final http.Response response = await http.post(
26 | Uri.parse(endpoint.url),
27 | body: jsonEncode(user.toJson(cachedId)),
28 | headers: {
29 | 'Content-Type': 'application/json',
30 | },
31 | );
32 |
33 | logUO(response.body.toString(), emoji: '👀');
34 |
35 | return jsonDecode(response.body)['id'];
36 | }
37 |
38 | static Future> getFeatures({
39 | required String projectId,
40 | required String userId,
41 | }) async {
42 | final Endpoint endpoint = RestfulEndpoints.features(
43 | apiKey: projectId,
44 | userId: userId,
45 | );
46 |
47 | final http.Response response = await http.get(
48 | Uri.parse(endpoint.url),
49 | );
50 |
51 | // TODO: throws user not found when an old project's user has been used, sync user before, check if it exists then continue
52 |
53 | final List features =
54 | (jsonDecode(response.body)['features'] as List)
55 | .map((feature) => Feature.fromJson(feature))
56 | .toList();
57 |
58 | features.sort((a, b) => b.voteCount.compareTo(a.voteCount));
59 |
60 | return features;
61 | }
62 |
63 | static Future toggleUpvote({
64 | required String projectId,
65 | required String userId,
66 | required Feature feature,
67 | }) async {
68 | final Endpoint endpoint = RestfulEndpoints.toggleUpvote(
69 | projectId: projectId,
70 | userId: userId,
71 | featureId: feature.id,
72 | );
73 |
74 | await http.post(
75 | Uri.parse(endpoint.url),
76 | body: jsonEncode(endpoint.body),
77 | headers: {
78 | 'Content-Type': 'application/json',
79 | },
80 | );
81 | }
82 |
83 | static Future sendFeatureRequest({
84 | required String projectId,
85 | required String userId,
86 | required String content,
87 | }) async {
88 | final Endpoint endpoint = RestfulEndpoints.sendFeedback(
89 | projectId: projectId,
90 | content: content,
91 | userId: userId,
92 | );
93 |
94 | await http.post(
95 | Uri.parse(endpoint.url),
96 | headers: {
97 | 'Content-Type': 'application/json',
98 | },
99 | body: jsonEncode(endpoint.body),
100 | );
101 | }
102 |
103 | static Future resolveUserUuid({
104 | required String projectId,
105 | required User? user,
106 | }) async {
107 | final SharedPreferences prefs = await SharedPreferences.getInstance();
108 | final String? cachedUuid = prefs.getString('user_orient_user_uuid');
109 |
110 | if (cachedUuid != null) {
111 | logUO('Found cached UUID: $cachedUuid', emoji: '🔍');
112 |
113 | UserOrientData.syncUser(
114 | user: user,
115 | projectId: projectId,
116 | cachedId: cachedUuid,
117 | ).ignore();
118 |
119 | return cachedUuid;
120 | } else {
121 | final UserUUID uuid = await UserOrientData.syncUser(
122 | user: user,
123 | cachedId: null,
124 | projectId: projectId,
125 | );
126 |
127 | await prefs.setString('user_orient_user_uuid', uuid);
128 |
129 | logUO('Acquired a new UUID: $uuid', emoji: '🆕');
130 |
131 | return uuid;
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/lib/src/models/endpoint.dart:
--------------------------------------------------------------------------------
1 | class Endpoint {
2 | final String url;
3 | final String method;
4 | final dynamic body;
5 |
6 | const Endpoint({
7 | required this.url,
8 | required this.method,
9 | this.body,
10 | });
11 |
12 | factory Endpoint.get({
13 | required String url,
14 | dynamic body,
15 | }) {
16 | return Endpoint(
17 | url: url,
18 | method: 'GET',
19 | body: body,
20 | );
21 | }
22 |
23 | factory Endpoint.post({
24 | required String url,
25 | dynamic body,
26 | }) {
27 | return Endpoint(
28 | url: url,
29 | method: 'POST',
30 | body: body,
31 | );
32 | }
33 |
34 | factory Endpoint.put({
35 | required String url,
36 | dynamic body,
37 | }) {
38 | return Endpoint(
39 | url: url,
40 | method: 'PUT',
41 | body: body,
42 | );
43 | }
44 |
45 | factory Endpoint.delete({
46 | required String url,
47 | dynamic body,
48 | }) {
49 | return Endpoint(
50 | url: url,
51 | method: 'DELETE',
52 | body: body,
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/models/feature.dart:
--------------------------------------------------------------------------------
1 | import 'package:userorient_flutter/src/models/label.dart';
2 |
3 | class Feature {
4 | final String id;
5 | final String status;
6 | final String projectId;
7 | final String ownerType;
8 | final String? ownerFirstName;
9 | final String? ownerLastName;
10 | final int voteCount;
11 | final DateTime? createdAt;
12 | final bool voted;
13 | final Map title;
14 | final Map description;
15 | final List? labels;
16 |
17 | String titleForLocale(String? locale) {
18 | return title[locale] ?? title['en'] ?? 'N/A';
19 | }
20 |
21 | String descriptionForLocale(String? locale) {
22 | return description[locale] ?? description['en'] ?? 'N/A';
23 | }
24 |
25 | bool get isSkeleton => id == 'skeleton';
26 |
27 | factory Feature.skeleton() {
28 | return Feature(
29 | id: 'skeleton',
30 | status: 'skeleton',
31 | projectId: 'skeleton',
32 | ownerType: 'skeleton',
33 | ownerFirstName: 'skeleton',
34 | ownerLastName: 'skeleton',
35 | voteCount: 0,
36 | createdAt: DateTime.now(),
37 | voted: false,
38 | title: {},
39 | description: {},
40 | labels: [],
41 | );
42 | }
43 |
44 | Feature({
45 | required this.id,
46 | required this.status,
47 | required this.projectId,
48 | required this.ownerType,
49 | this.ownerFirstName,
50 | this.ownerLastName,
51 | required this.voteCount,
52 | required this.createdAt,
53 | required this.voted,
54 | required this.title,
55 | required this.description,
56 | required this.labels,
57 | });
58 |
59 | Feature copyWith({
60 | String? id,
61 | String? status,
62 | String? projectId,
63 | String? ownerType,
64 | String? ownerFirstName,
65 | String? ownerLastName,
66 | int? voteCount,
67 | DateTime? createdAt,
68 | bool? voted,
69 | Map? title,
70 | Map? description,
71 | List? labels,
72 | }) {
73 | return Feature(
74 | id: id ?? this.id,
75 | status: status ?? this.status,
76 | projectId: projectId ?? this.projectId,
77 | ownerType: ownerType ?? this.ownerType,
78 | ownerFirstName: ownerFirstName ?? this.ownerFirstName,
79 | ownerLastName: ownerLastName ?? this.ownerLastName,
80 | voteCount: voteCount ?? this.voteCount,
81 | createdAt: createdAt ?? this.createdAt,
82 | voted: voted ?? this.voted,
83 | title: title ?? this.title,
84 | description: description ?? this.description,
85 | labels: labels ?? this.labels,
86 | );
87 | }
88 |
89 | factory Feature.fromJson(Map json) {
90 | return Feature(
91 | id: json['id'],
92 | status: json['status'],
93 | projectId: json['projectId'],
94 | ownerType: json['ownerType'],
95 | ownerFirstName: json['ownerFirstName'],
96 | ownerLastName: json['ownerLastName'],
97 | voteCount: json['voteCount'],
98 | createdAt:
99 | json['createdAt'] == null ? null : DateTime.parse(json['createdAt']),
100 | voted: json['voted'],
101 | title: json['title'],
102 | description: json['description'],
103 | labels: json['labels'] != null
104 | ? List.from(json['labels'].map((x) => Label.fromJson(x)))
105 | : [],
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/src/models/label.dart:
--------------------------------------------------------------------------------
1 | class Label {
2 | final String id;
3 | final String color;
4 | final Map name;
5 |
6 | const Label({
7 | required this.id,
8 | required this.color,
9 | required this.name,
10 | });
11 |
12 | factory Label.fromJson(Map json) {
13 | return Label(
14 | id: json['id'],
15 | color: json['color'],
16 | name: json['name'],
17 | );
18 | }
19 |
20 | bool get isCompleted => id == '07d82cf0-51ea-45d5-b274-59edb1b11a20';
21 |
22 | @override
23 | bool operator ==(Object other) {
24 | if (identical(this, other)) return true;
25 |
26 | return other is Label && other.id == id;
27 | }
28 |
29 | @override
30 | int get hashCode => id.hashCode;
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/models/project.dart:
--------------------------------------------------------------------------------
1 | class Project {
2 | final String? id;
3 | final String? name;
4 | final String? logoUrl;
5 | final String? color;
6 |
7 | Project({
8 | required this.id,
9 | required this.name,
10 | required this.logoUrl,
11 | required this.color,
12 | });
13 |
14 | factory Project.fromJson(Map json) {
15 | return Project(
16 | id: json['id'],
17 | name: json['name'],
18 | logoUrl: json['logoUrl'],
19 | color: json['color'],
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/models/user.dart:
--------------------------------------------------------------------------------
1 | class User {
2 | final String? uniqueIdentifier;
3 | final String? fullName;
4 | final String? email;
5 | final String? phoneNumber;
6 | final String? language;
7 | final Map? extra;
8 |
9 | const User({
10 | this.uniqueIdentifier,
11 | this.fullName,
12 | this.email,
13 | this.phoneNumber,
14 | this.language,
15 | this.extra,
16 | });
17 |
18 | bool get isAnonymous => uniqueIdentifier == null;
19 |
20 | const User.anonymous()
21 | : uniqueIdentifier = null,
22 | fullName = null,
23 | email = null,
24 | phoneNumber = null,
25 | language = null,
26 | extra = null;
27 |
28 | Map toJson(String? id) {
29 | return {
30 | 'userId': id,
31 | 'uniqueIdentifier': uniqueIdentifier,
32 | 'fullName': fullName,
33 | 'email': email,
34 | 'phoneNumber': phoneNumber,
35 | 'language': language,
36 | 'extra': extra,
37 | };
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/utilities/build_context_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | extension BuildContextX on BuildContext {
4 | bool get isDark => Theme.of(this).brightness == Brightness.dark;
5 | Color get backgroundColor => isDark ? const Color(0xff1D1D1D) : Colors.white;
6 | Color get textColor =>
7 | isDark ? const Color(0xffFAFAFA) : const Color(0xff2A2A2A);
8 | Color get secondaryTextColor =>
9 | isDark ? const Color(0xffB2B2B2) : const Color(0xffACAEAF);
10 | Color get borderColor =>
11 | isDark ? const Color(0xff303030) : const Color(0xffF2F2F2);
12 |
13 | Color get votedColor =>
14 | isDark ? const Color(0xff223027) : const Color(0xffEEFCF2);
15 | Color get unvotedColor =>
16 | isDark ? const Color(0xff2C2C2C) : const Color(0xffF7F7F7);
17 | Color get votedArrowColor =>
18 | isDark ? const Color(0xff52DF82) : const Color(0xff52DF82);
19 | Color get buttonColor =>
20 | isDark ? const Color(0xffFAFAFA) : const Color(0xff2A2A2A);
21 | Color get buttonTextColor => isDark ? const Color(0xff1D1D1D) : Colors.white;
22 | Color get unvotedContainerColor => isDark
23 | ? const Color(0xffB2B2B2).withOpacity(.1)
24 | : const Color(0xffE9EAEE).withOpacity(.75);
25 | Color get votedContainerColor =>
26 | isDark ? Colors.white.withOpacity(.75) : const Color(0xff2F313F);
27 | Color get completedContainerColor =>
28 | isDark ? const Color(0xff223027) : const Color(0xffDCF9E6);
29 | Color get tabsBackgroundColor =>
30 | isDark ? const Color(0xff121212) : const Color(0xffFAFAFA);
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/utilities/helper_functions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 |
3 | import 'package:flutter/foundation.dart';
4 |
5 | void logUO(
6 | String message, {
7 | required String emoji,
8 | }) {
9 | if (kDebugMode) {
10 | log('$emoji $message', name: 'U/O');
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/utilities/navigation.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/foundation.dart';
4 |
5 | class Navigation {
6 | static final GlobalKey _webNavigatorKey =
7 | GlobalKey();
8 |
9 | static Future push(BuildContext context, Widget child) {
10 | if (defaultTargetPlatform == TargetPlatform.iOS) {
11 | return Navigator.of(context).push(
12 | CupertinoSheetRoute(
13 | builder: (_) => child,
14 | ),
15 | );
16 | }
17 |
18 | if (defaultTargetPlatform == TargetPlatform.android) {
19 | return Navigator.of(context).push(
20 | MaterialPageRoute(
21 | builder: (_) => child,
22 | ),
23 | );
24 | }
25 |
26 | // For web, if dialog is already showing, use internal navigation
27 | if (Navigator.of(context)
28 | .overlay
29 | ?.context
30 | .findAncestorWidgetOfExactType<_WebDialog>() !=
31 | null) {
32 | return _webNavigatorKey.currentState!
33 | .push(MaterialPageRoute(builder: (_) => child));
34 | }
35 |
36 | // Otherwise show the dialog with internal navigation
37 | return showDialog(
38 | context: context,
39 | barrierDismissible: true,
40 | barrierColor: Colors.black54,
41 | builder: (context) => _WebDialog(child: child),
42 | );
43 | }
44 | }
45 |
46 | class _WebDialog extends StatefulWidget {
47 | final Widget child;
48 |
49 | const _WebDialog({required this.child});
50 |
51 | @override
52 | State<_WebDialog> createState() => _WebDialogState();
53 | }
54 |
55 | class _WebDialogState extends State<_WebDialog>
56 | with SingleTickerProviderStateMixin {
57 | late final AnimationController _controller;
58 | late final Animation _slideAnimation;
59 | late final Animation _fadeAnimation;
60 |
61 | @override
62 | void initState() {
63 | super.initState();
64 | _controller = AnimationController(
65 | duration: const Duration(milliseconds: 450),
66 | vsync: this,
67 | );
68 |
69 | final curve = CurvedAnimation(
70 | parent: _controller,
71 | curve: Curves.easeOutQuart,
72 | );
73 |
74 | _slideAnimation = Tween(
75 | begin: const Offset(1, 0),
76 | end: Offset.zero,
77 | ).animate(curve);
78 |
79 | _fadeAnimation = CurvedAnimation(
80 | parent: _controller,
81 | curve: const Interval(0.0, 0.7, curve: Curves.easeOut),
82 | );
83 |
84 | _controller.forward();
85 | }
86 |
87 | @override
88 | void dispose() {
89 | _controller.dispose();
90 | super.dispose();
91 | }
92 |
93 | @override
94 | Widget build(BuildContext context) {
95 | return FadeTransition(
96 | opacity: _fadeAnimation,
97 | child: SlideTransition(
98 | position: _slideAnimation,
99 | child: Align(
100 | alignment: Alignment.centerRight,
101 | child: Padding(
102 | padding: const EdgeInsets.all(16),
103 | child: Material(
104 | elevation: 16,
105 | borderRadius: BorderRadius.circular(16),
106 | child: ClipRRect(
107 | borderRadius: BorderRadius.circular(16),
108 | child: SizedBox(
109 | width: 480,
110 | height: double.infinity,
111 | child: Navigator(
112 | key: Navigation._webNavigatorKey,
113 | onGenerateRoute: (settings) => MaterialPageRoute(
114 | builder: (_) => widget.child,
115 | ),
116 | ),
117 | ),
118 | ),
119 | ),
120 | ),
121 | ),
122 | ),
123 | );
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/lib/src/utilities/restful_endpoints.dart:
--------------------------------------------------------------------------------
1 | import 'package:userorient_flutter/src/models/endpoint.dart';
2 |
3 | class RestfulEndpoints {
4 | const RestfulEndpoints._();
5 |
6 | static const String baseUrl = 'https://api.userorient.com';
7 |
8 | static Endpoint projectDetails(String projectId) {
9 | return Endpoint.get(
10 | url: '$baseUrl/sdk/project/details?projectId=$projectId',
11 | );
12 | }
13 |
14 | static Endpoint features({
15 | required String apiKey,
16 | required String userId,
17 | }) {
18 | return Endpoint.get(
19 | url: '$baseUrl/sdk/feature/all?projectId=$apiKey&userId=$userId',
20 | );
21 | }
22 |
23 | static Endpoint syncUser(String projectId) {
24 | return Endpoint.post(
25 | url: '$baseUrl/sdk/user/sync?projectId=$projectId',
26 | );
27 | }
28 |
29 | static Endpoint toggleUpvote({
30 | required String projectId,
31 | required String userId,
32 | required String featureId,
33 | }) {
34 | return Endpoint.post(
35 | url: '$baseUrl/sdk/feature/toggle?projectId=$projectId',
36 | body: {
37 | 'userId': userId,
38 | 'featureId': featureId,
39 | },
40 | );
41 | }
42 |
43 | // {{base_url}}/sdk/feedback?projectId={{project_id}}
44 | static Endpoint sendFeedback({
45 | required String projectId,
46 | required String content,
47 | required String userId,
48 | }) {
49 | return Endpoint.post(
50 | url: '$baseUrl/sdk/feedback?projectId=$projectId',
51 | body: {
52 | 'userId': userId,
53 | 'description': {
54 | 'en': content,
55 | },
56 | },
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/src/views/board_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:userorient_flutter/src/logic/l10n.dart';
4 | import 'package:userorient_flutter/src/models/feature.dart';
5 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
6 | import 'package:userorient_flutter/src/widgets/feature_card.dart';
7 | import 'package:userorient_flutter/src/widgets/styled_close_button.dart';
8 | import 'package:userorient_flutter/src/widgets/tip_card.dart';
9 | import 'package:userorient_flutter/src/widgets/watermark.dart';
10 | import 'package:userorient_flutter/userorient_flutter.dart';
11 |
12 | class BoardView extends StatefulWidget {
13 | const BoardView({super.key});
14 |
15 | @override
16 | State createState() => _BoardViewState();
17 | }
18 |
19 | class _BoardViewState extends State {
20 | late final ScrollController _scrollController;
21 | int _index = 0;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _scrollController = ScrollController();
27 | }
28 |
29 | @override
30 | void dispose() {
31 | super.dispose();
32 | _scrollController.dispose();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Scaffold(
38 | backgroundColor: context.backgroundColor,
39 | appBar: AppBar(
40 | backgroundColor: context.backgroundColor,
41 | elevation: 0.0,
42 | automaticallyImplyLeading: false,
43 | surfaceTintColor: Colors.transparent,
44 | title: Padding(
45 | padding: const EdgeInsets.only(left: 4.0),
46 | child: Text(
47 | L10n.title,
48 | style: TextStyle(
49 | fontSize: 20.0,
50 | height: 28 / 20,
51 | fontWeight: FontWeight.bold,
52 | color: context.textColor,
53 | ),
54 | ),
55 | ),
56 | centerTitle: false,
57 | actions: const [
58 | StyledCloseButton(),
59 | SizedBox(width: 12.0),
60 | ],
61 | ),
62 | body: Column(
63 | children: [
64 | const SizedBox(height: 8),
65 | _Tabs(
66 | index: _index,
67 | onIndexChanged: (index) {
68 | setState(() => _index = index);
69 | },
70 | ),
71 | const SizedBox(height: 8),
72 | Expanded(
73 | child: ValueListenableBuilder(
74 | valueListenable: UserOrient.features,
75 | builder: (context, List? features, _) {
76 | features ??= List.generate(7, (index) {
77 | return Feature.skeleton();
78 | });
79 |
80 | // sort features by status
81 | final List sortedFeatures = features.toList()
82 | ..removeWhere((feature) {
83 | final bool isCompleted = feature.labels?.any(
84 | (label) {
85 | return label.id ==
86 | '07d82cf0-51ea-45d5-b274-59edb1b11a20';
87 | },
88 | ) ??
89 | false;
90 |
91 | return _index == 0 ? isCompleted : !isCompleted;
92 | });
93 |
94 | return MediaQuery.removePadding(
95 | context: context,
96 | removeBottom: true,
97 | child: Scrollbar(
98 | child: _List(
99 | features: sortedFeatures,
100 | ),
101 | ),
102 | );
103 | },
104 | ),
105 | ),
106 | const Watermark(),
107 | ],
108 | ),
109 | );
110 | }
111 | }
112 |
113 | class _List extends StatelessWidget {
114 | final List features;
115 |
116 | const _List({
117 | required this.features,
118 | });
119 |
120 | @override
121 | Widget build(BuildContext context) {
122 | return ListView.separated(
123 | padding: const EdgeInsets.only(
124 | left: 16.0,
125 | right: 16.0,
126 | top: 8.0,
127 | bottom: 16.0,
128 | ),
129 | itemCount: features.length + 1,
130 | itemBuilder: (context, index) {
131 | if (index == 0) {
132 | return const TipCard();
133 | }
134 |
135 | final Feature feature = features[index - 1];
136 |
137 | return FeatureCard(
138 | feature,
139 | isShimmer: feature.isSkeleton,
140 | );
141 | },
142 | separatorBuilder: (context, index) {
143 | if (index == 0) {
144 | return const SizedBox(height: 12.0);
145 | }
146 |
147 | return const SizedBox(height: 4.0);
148 | },
149 | );
150 | }
151 | }
152 |
153 | class _Tabs extends StatelessWidget {
154 | final int index;
155 | final Function(int) onIndexChanged;
156 |
157 | const _Tabs({
158 | required this.index,
159 | required this.onIndexChanged,
160 | });
161 |
162 | @override
163 | Widget build(BuildContext context) {
164 | return Container(
165 | padding: const EdgeInsets.all(2),
166 | decoration: BoxDecoration(
167 | color: context.tabsBackgroundColor,
168 | borderRadius: BorderRadius.circular(10),
169 | border: Border.all(
170 | color: context.borderColor,
171 | ),
172 | ),
173 | child: Row(
174 | mainAxisSize: MainAxisSize.min,
175 | children: [
176 | _Tab(
177 | label: L10n.roadmap,
178 | isActive: index == 0,
179 | onTap: () => onIndexChanged(0),
180 | ),
181 | const SizedBox(width: 2),
182 | _Tab(
183 | label: L10n.implemented,
184 | isActive: index == 1,
185 | onTap: () => onIndexChanged(1),
186 | ),
187 | ],
188 | ),
189 | );
190 | }
191 | }
192 |
193 | class _Tab extends StatelessWidget {
194 | final String label;
195 | final bool isActive;
196 | final Function() onTap;
197 |
198 | const _Tab({
199 | required this.label,
200 | required this.isActive,
201 | required this.onTap,
202 | });
203 |
204 | @override
205 | Widget build(BuildContext context) {
206 | return GestureDetector(
207 | onTap: onTap,
208 | child: AnimatedContainer(
209 | height: 32,
210 | duration: const Duration(milliseconds: 100),
211 | alignment: Alignment.center,
212 | padding: const EdgeInsets.symmetric(horizontal: 24),
213 | decoration: BoxDecoration(
214 | color: isActive ? context.buttonColor : Colors.transparent,
215 | borderRadius: BorderRadius.circular(8),
216 | ),
217 | child: Text(
218 | label,
219 | style: TextStyle(
220 | fontSize: 14,
221 | fontWeight: FontWeight.w500,
222 | color: isActive ? context.buttonTextColor : context.textColor,
223 | ),
224 | ),
225 | ),
226 | );
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/lib/src/views/form_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:userorient_flutter/src/logic/l10n.dart';
3 | import 'package:userorient_flutter/src/logic/user_orient.dart';
4 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
5 | import 'package:userorient_flutter/src/utilities/navigation.dart';
6 | import 'package:userorient_flutter/src/views/sent_view.dart';
7 | import 'package:userorient_flutter/src/widgets/bottom_padding.dart';
8 | import 'package:userorient_flutter/src/widgets/button.dart';
9 | import 'package:userorient_flutter/src/widgets/styled_close_button.dart';
10 | import 'package:userorient_flutter/src/widgets/styled_text_field.dart';
11 |
12 | class FormView extends StatefulWidget {
13 | const FormView({super.key});
14 |
15 | @override
16 | State createState() => FormViewState();
17 | }
18 |
19 | class FormViewState extends State {
20 | late final TextEditingController _controller;
21 | bool _isLoading = false;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _controller = TextEditingController();
27 | }
28 |
29 | @override
30 | void dispose() {
31 | super.dispose();
32 | _controller.dispose();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Scaffold(
38 | backgroundColor: context.backgroundColor,
39 | appBar: AppBar(
40 | backgroundColor: context.backgroundColor,
41 | automaticallyImplyLeading: false,
42 | centerTitle: true,
43 | title: Text(
44 | L10n.addFeature,
45 | style: TextStyle(
46 | fontSize: 16.0,
47 | fontWeight: FontWeight.w700,
48 | color: context.textColor,
49 | ),
50 | ),
51 | actions: const [
52 | StyledCloseButton(),
53 | SizedBox(width: 12.0),
54 | ],
55 | ),
56 | body: Column(
57 | children: [
58 | Expanded(
59 | child: Padding(
60 | padding: const EdgeInsets.symmetric(horizontal: 12.0),
61 | child: StyledTextField(
62 | minLines: 20,
63 | controller: _controller,
64 | hintText: L10n.formHint,
65 | autoFocus: true,
66 | ),
67 | ),
68 | ),
69 | const SizedBox(height: 24.0),
70 | Button(
71 | onPressed: () {
72 | final String content = _controller.text.trim();
73 |
74 | if (content.isEmpty) {
75 | ScaffoldMessenger.of(context).hideCurrentSnackBar();
76 | ScaffoldMessenger.of(context).showSnackBar(
77 | SnackBar(
78 | elevation: 0,
79 | content: Text(
80 | L10n.formEmpty,
81 | textAlign: TextAlign.center,
82 | style: const TextStyle(
83 | fontSize: 16.0,
84 | fontWeight: FontWeight.w500,
85 | color: Colors.red,
86 | ),
87 | ),
88 | margin: const EdgeInsets.only(
89 | bottom: 120,
90 | left: 24,
91 | right: 24,
92 | ),
93 | padding: const EdgeInsets.symmetric(vertical: 8),
94 | behavior: SnackBarBehavior.floating,
95 | backgroundColor: Colors.red.shade100,
96 | shape: RoundedRectangleBorder(
97 | borderRadius: BorderRadius.circular(8.0),
98 | ),
99 | ),
100 | );
101 |
102 | return;
103 | }
104 |
105 | setState(() {
106 | _isLoading = true;
107 | });
108 |
109 | UserOrient.submitForm(content: content).then((_) {
110 | setState(() {
111 | Navigator.pop(context);
112 | Navigation.push(context, const SentView());
113 | });
114 | });
115 | },
116 | busy: _isLoading,
117 | label: L10n.submitForm,
118 | ),
119 | const BottomPadding(),
120 | ],
121 | ),
122 | );
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/src/views/sent_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_svg/flutter_svg.dart';
3 | import 'package:userorient_flutter/src/logic/l10n.dart';
4 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
5 | import 'package:userorient_flutter/src/widgets/bottom_padding.dart';
6 | import 'package:userorient_flutter/src/widgets/button.dart';
7 |
8 | class SentView extends StatelessWidget {
9 | const SentView({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Scaffold(
14 | backgroundColor: context.backgroundColor,
15 | body: const _Body(),
16 | );
17 | }
18 | }
19 |
20 | class _Body extends StatelessWidget {
21 | const _Body();
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return SizedBox(
26 | width: double.maxFinite,
27 | child: Column(
28 | children: [
29 | Expanded(
30 | child: Padding(
31 | padding: const EdgeInsets.symmetric(horizontal: 12.0),
32 | child: Column(
33 | mainAxisAlignment: MainAxisAlignment.center,
34 | children: [
35 | SizedBox(
36 | height: 80.0,
37 | width: 80.0,
38 | child: SvgPicture.asset(
39 | 'assets/check.svg',
40 | package: 'userorient_flutter',
41 | ),
42 | ),
43 | const SizedBox(height: 24.0),
44 | Text(
45 | L10n.sentTitle,
46 | style: TextStyle(
47 | fontSize: 20.0,
48 | height: 28 / 20,
49 | color: context.textColor,
50 | ),
51 | ),
52 | const SizedBox(height: 16.0),
53 | Padding(
54 | padding: const EdgeInsets.symmetric(horizontal: 40.0),
55 | child: Text(
56 | L10n.sentDescription,
57 | textAlign: TextAlign.center,
58 | style: TextStyle(
59 | fontSize: 14.0,
60 | height: 20 / 14,
61 | color: context.secondaryTextColor,
62 | ),
63 | ),
64 | ),
65 | ],
66 | ),
67 | ),
68 | ),
69 | const SizedBox(height: 24.0),
70 | Button(
71 | onPressed: () {
72 | Navigator.of(context).pop();
73 | },
74 | label: L10n.goBack,
75 | ),
76 | const BottomPadding(32),
77 | ],
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/src/widgets/bottom_padding.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class BottomPadding extends StatelessWidget {
5 | final double defaultHeight;
6 |
7 | const BottomPadding([
8 | this.defaultHeight = 16.0,
9 | key,
10 | ]) : super(key: key);
11 |
12 | static double of(
13 | BuildContext context, {
14 | double defaultHeight = 16.0,
15 | }) {
16 | final isAndroid = defaultTargetPlatform == TargetPlatform.android;
17 | final bottomPadding =
18 | isAndroid ? defaultHeight : MediaQuery.of(context).padding.bottom;
19 | final height = bottomPadding > 0 ? bottomPadding : defaultHeight;
20 |
21 | return height;
22 | }
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | final height = of(context, defaultHeight: defaultHeight);
27 |
28 | return SizedBox(height: height);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/widgets/button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
3 |
4 | class Button extends StatelessWidget {
5 | final String label;
6 | final VoidCallback onPressed;
7 | final Widget? icon;
8 | final bool busy;
9 |
10 | const Button({
11 | super.key,
12 | required this.label,
13 | required this.onPressed,
14 | this.busy = false,
15 | this.icon,
16 | });
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return GestureDetector(
21 | onTap: () {
22 | if (!busy) {
23 | onPressed();
24 | }
25 | },
26 | behavior: HitTestBehavior.translucent,
27 | child: Container(
28 | height: 56.0,
29 | margin: const EdgeInsets.symmetric(horizontal: 16.0),
30 | alignment: Alignment.center,
31 | width: double.infinity,
32 | decoration: BoxDecoration(
33 | borderRadius: BorderRadius.circular(12.0),
34 | color: context.buttonColor,
35 | ),
36 | child: busy
37 | ? Container(
38 | alignment: Alignment.center,
39 | width: 24.0,
40 | height: 24.0,
41 | child: CircularProgressIndicator(
42 | valueColor: AlwaysStoppedAnimation(
43 | context.buttonTextColor,
44 | ),
45 | strokeWidth: 3.0,
46 | ),
47 | )
48 | : Row(
49 | mainAxisAlignment: MainAxisAlignment.center,
50 | children: [
51 | if (icon != null) ...[
52 | icon!,
53 | const SizedBox(width: 8.0),
54 | ],
55 | Text(
56 | label,
57 | style: TextStyle(
58 | color: context.buttonTextColor,
59 | fontSize: 16.0,
60 | fontWeight: FontWeight.w500,
61 | ),
62 | ),
63 | ],
64 | ),
65 | ),
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/src/widgets/feature_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:flutter_svg/flutter_svg.dart';
4 |
5 | import 'package:userorient_flutter/src/models/feature.dart';
6 | import 'package:userorient_flutter/src/models/label.dart';
7 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
8 | import 'package:userorient_flutter/userorient_flutter.dart';
9 |
10 | class FeatureCard extends StatelessWidget {
11 | final Feature feature;
12 | final bool isShimmer;
13 |
14 | const FeatureCard(
15 | this.feature, {
16 | super.key,
17 | required this.isShimmer,
18 | });
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return SizedBox(
23 | key: ValueKey(feature.id),
24 | child: Container(
25 | padding: const EdgeInsets.all(12),
26 | decoration: BoxDecoration(
27 | border: Border.all(
28 | color: context.borderColor,
29 | width: 1.0,
30 | ),
31 | borderRadius: BorderRadius.circular(16.0),
32 | ),
33 | child: isShimmer ? _buildShimmer() : _buildWidget(context),
34 | ),
35 | );
36 | }
37 |
38 | Row _buildWidget(BuildContext context) {
39 | final bool isCompleted = feature.labels?.any(
40 | (label) => label.id == '07d82cf0-51ea-45d5-b274-59edb1b11a20',
41 | ) ??
42 | false;
43 |
44 | return Row(
45 | crossAxisAlignment: CrossAxisAlignment.start,
46 | children: [
47 | GestureDetector(
48 | onTap: () {
49 | if (isCompleted) return;
50 |
51 | UserOrient.toggleUpvote(feature);
52 |
53 | if (feature.voted) {
54 | HapticFeedback.lightImpact();
55 | } else {
56 | HapticFeedback.mediumImpact();
57 | }
58 | },
59 | behavior: HitTestBehavior.translucent,
60 | child: AnimatedContainer(
61 | duration: const Duration(milliseconds: 100),
62 | height: 56.0,
63 | width: 56.0,
64 | margin: const EdgeInsets.only(top: 4.0),
65 | decoration: BoxDecoration(
66 | color: isCompleted
67 | ? context.completedContainerColor
68 | : feature.voted
69 | ? context.votedContainerColor
70 | : context.unvotedContainerColor,
71 | borderRadius: BorderRadius.circular(12.0),
72 | ),
73 | alignment: Alignment.center,
74 | child: isCompleted
75 | ? Transform.scale(
76 | scale: 56 / 48,
77 | child: SvgPicture.asset(
78 | 'assets/completed-mark.svg',
79 | package: 'userorient_flutter',
80 | ),
81 | )
82 | : Column(
83 | children: [
84 | const SizedBox(height: 5.0),
85 | Transform.scale(
86 | scale: 56 / 48,
87 | child: SvgPicture.asset(
88 | feature.voted
89 | ? 'assets/upvote-on.svg'
90 | : 'assets/upvote-off.svg',
91 | package: 'userorient_flutter',
92 | colorFilter: feature.voted
93 | ? ColorFilter.mode(
94 | context.buttonTextColor,
95 | BlendMode.srcIn,
96 | )
97 | : const ColorFilter.mode(
98 | Color(0xffA9ABB9),
99 | BlendMode.srcIn,
100 | ),
101 | ),
102 | ),
103 | Text(
104 | feature.voteCount.toString(),
105 | style: TextStyle(
106 | fontSize: 14.0,
107 | height: 20 / 14,
108 | color: feature.voted
109 | ? context.buttonTextColor
110 | : context.textColor,
111 | fontWeight: FontWeight.w400,
112 | ),
113 | ),
114 | ],
115 | ),
116 | ),
117 | ),
118 | const SizedBox(width: 12.0),
119 | Expanded(
120 | child: Column(
121 | crossAxisAlignment: CrossAxisAlignment.start,
122 | children: [
123 | Text(
124 | feature.titleForLocale(UserOrient.user?.language),
125 | style: TextStyle(
126 | fontSize: 16.0,
127 | height: 24 / 16,
128 | color: context.textColor,
129 | fontWeight: FontWeight.bold,
130 | ),
131 | ),
132 | const SizedBox(height: 2.0),
133 | Text(
134 | feature.descriptionForLocale(UserOrient.user?.language),
135 | style: TextStyle(
136 | fontSize: 14.0,
137 | height: 20 / 14,
138 | color: context.secondaryTextColor,
139 | ),
140 | ),
141 | _LabelRow(
142 | labels: feature.labels,
143 | ),
144 | ],
145 | ),
146 | ),
147 | const SizedBox(width: 8.0),
148 | ],
149 | );
150 | }
151 |
152 | Widget _buildShimmer() {
153 | return Row(
154 | crossAxisAlignment: CrossAxisAlignment.start,
155 | children: [
156 | Container(
157 | height: 56.0,
158 | width: 56.0,
159 | decoration: BoxDecoration(
160 | color: const Color(0xffE9EAEE),
161 | borderRadius: BorderRadius.circular(12.0),
162 | ),
163 | ),
164 | const SizedBox(width: 16.0),
165 | Expanded(
166 | child: Column(
167 | crossAxisAlignment: CrossAxisAlignment.start,
168 | children: [
169 | Container(
170 | height: 24.0,
171 | width: 160.0,
172 | decoration: BoxDecoration(
173 | color: const Color(0xffE9EAEE),
174 | borderRadius: BorderRadius.circular(8.0),
175 | ),
176 | ),
177 | const SizedBox(height: 2.0),
178 | Container(
179 | height: 20.0,
180 | width: 240.0,
181 | decoration: BoxDecoration(
182 | color: const Color(0xffE9EAEE),
183 | borderRadius: BorderRadius.circular(7.0),
184 | ),
185 | ),
186 | ],
187 | ),
188 | ),
189 | const SizedBox(width: 8.0),
190 | ],
191 | );
192 | }
193 | }
194 |
195 | class _LabelRow extends StatelessWidget {
196 | final List? labels;
197 |
198 | const _LabelRow({
199 | required this.labels,
200 | });
201 |
202 | @override
203 | Widget build(BuildContext context) {
204 | if (labels == null || labels!.isEmpty) {
205 | return const SizedBox.shrink();
206 | }
207 |
208 | return Row(
209 | children: [
210 | for (final Label label in labels!)
211 | if (!label.isCompleted)
212 | Container(
213 | margin: const EdgeInsets.only(right: 8.0, top: 8.0),
214 | padding: const EdgeInsets.symmetric(
215 | horizontal: 6.0,
216 | vertical: 4.0,
217 | ),
218 | decoration: BoxDecoration(
219 | color: Color(
220 | int.parse('0xff${label.color.replaceAll('#', '')}'),
221 | ).withOpacity(.1),
222 | borderRadius: BorderRadius.circular(6.0),
223 | ),
224 | child: Text(
225 | label.name[UserOrient.languageCode] ?? label.name['en'],
226 | style: TextStyle(
227 | fontSize: 12.0,
228 | height: 16 / 12,
229 | color: Color(
230 | int.parse('0xff${label.color.replaceAll('#', '')}'),
231 | ),
232 | ),
233 | ),
234 | ),
235 | ],
236 | );
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/lib/src/widgets/image_fade.dart:
--------------------------------------------------------------------------------
1 | // From: image_fade package
2 |
3 | import 'package:flutter/widgets.dart';
4 | import 'dart:ui' as ui;
5 |
6 | /// Signature used by [ImageFade.errorBuilder] to build the widget that will be displayed
7 | /// if an error occurs while loading an image.
8 | typedef ImageFadeErrorBuilder = Widget Function(
9 | BuildContext context,
10 | Object exception,
11 | );
12 |
13 | /// Signature used by [ImageFade.loadingBuilder] to build the widget that will be displayed
14 | /// while an image is loading. `progress` returns a value between 0 and 1 indicating load progress.
15 | typedef ImageFadeLoadingBuilder = Widget Function(
16 | BuildContext context,
17 | double progress,
18 | ImageChunkEvent? chunkEvent,
19 | );
20 |
21 | /// A widget that displays a [placeholder] widget while a specified [image] loads,
22 | /// then cross-fades to the loaded image. Can optionally display loading progress
23 | /// and errors.
24 | ///
25 | /// If [image] is subsequently changed, it will cross-fade to the new image once it
26 | /// finishes loading.
27 | ///
28 | /// Setting [image] to null will cross-fade back to the [placeholder].
29 | ///
30 | /// ```dart
31 | /// ImageFade(
32 | /// placeholder: Image.asset('assets/myPlaceholder.png'),
33 | /// image: NetworkImage('https://backend.example.com/image.png'),
34 | /// )
35 | /// ```
36 | class ImageFade extends StatefulWidget {
37 | /// Creates a widget that displays a [placeholder] widget while a specified [image] loads,
38 | /// then cross-fades to the loaded image.
39 | const ImageFade({
40 | super.key,
41 | this.placeholder,
42 | this.image,
43 | this.curve = Curves.linear,
44 | this.duration = const Duration(milliseconds: 300),
45 | this.syncDuration,
46 | this.width,
47 | this.height,
48 | this.scale = 1,
49 | this.fit = BoxFit.scaleDown,
50 | this.alignment = Alignment.center,
51 | this.repeat = ImageRepeat.noRepeat,
52 | this.matchTextDirection = false,
53 | this.excludeFromSemantics = false,
54 | this.semanticLabel,
55 | this.loadingBuilder,
56 | this.errorBuilder,
57 | });
58 |
59 | /// Widget layered behind the loaded images. Displayed when [image] is null or is loading initially.
60 | final Widget? placeholder;
61 |
62 | /// The image to display. Subsequently changing the image will fade the new image over the previous one.
63 | final ImageProvider? image;
64 |
65 | /// The curve of the fade-in animation.
66 | final Curve curve;
67 |
68 | /// The duration of the fade-in animation.
69 | final Duration duration;
70 |
71 | /// An optional duration for fading in a synchronously loaded image (ex. from memory), error, or placeholder.
72 | /// For example, you could set this to `Duration.zero` to immediately display images that are already loaded.
73 | /// If omitted, [duration] will be used.
74 | final Duration? syncDuration;
75 |
76 | /// The width to display at. See [Image.width] for more information.
77 | final double? width;
78 |
79 | /// The height to display at. See [Image.height] for more information.
80 | final double? height;
81 |
82 | /// The scale factor for drawing this image at its intended size. See [RawImage.scale] for more information.
83 | final double scale;
84 |
85 | /// How to draw the image within its bounds. Defaults to [BoxFit.scaleDown]. See [Image.fit] for more information.
86 | final BoxFit fit;
87 |
88 | /// How to align the image within its bounds. See [Image.alignment] for more information.
89 | final Alignment alignment;
90 |
91 | /// How to paint any portions of the layout bounds not covered by the image. See [Image.repeat] for more information.
92 | final ImageRepeat repeat;
93 |
94 | /// Whether to paint the image in the direction of the [TextDirection]. See [Image.matchTextDirection] for more information.
95 | final bool matchTextDirection;
96 |
97 | /// Whether to exclude this image from semantics. See [Image.excludeFromSemantics] for more information.
98 | final bool excludeFromSemantics;
99 |
100 | /// A Semantic description of the image. See [Image.semanticLabel] for more information.
101 | final String? semanticLabel;
102 |
103 | /// A builder that specifies the widget to display while an image is loading.
104 | /// See [ImageFadeLoadingBuilder] for more information.
105 | final ImageFadeLoadingBuilder? loadingBuilder;
106 |
107 | /// A builder that specifies the widget to display if an error occurs while an image is loading.
108 | /// This will be faded in over previous content, so you may want to set an opaque background on it.
109 | final ImageFadeErrorBuilder? errorBuilder;
110 |
111 | @override
112 | State createState() => _ImageFadeState();
113 | }
114 |
115 | class _ImageFadeState extends State with TickerProviderStateMixin {
116 | _ImageResolver? _resolver;
117 | Widget? _front;
118 | Widget? _back;
119 |
120 | late final AnimationController _controller;
121 | Widget? _fadeFront;
122 | Widget? _fadeBack;
123 |
124 | bool? _sync; // could use onImage synchronousCall, but this is more forgiving
125 | bool _shouldBuildFront = false;
126 |
127 | @override
128 | void initState() {
129 | _controller = AnimationController(vsync: this);
130 | super.initState();
131 | }
132 |
133 | @override
134 | void didChangeDependencies() {
135 | super.didChangeDependencies();
136 | // Can't call this in initState because createLocalImageConfiguration throws errors:
137 | _update(context);
138 | }
139 |
140 | @override
141 | void didUpdateWidget(ImageFade old) {
142 | // not called on init
143 | super.didUpdateWidget(old);
144 | _update(context, old);
145 | }
146 |
147 | void _update(BuildContext context, [ImageFade? old]) {
148 | final ImageProvider? image = widget.image;
149 | final ImageProvider? oldImage = old?.image;
150 | if (image == oldImage) return;
151 |
152 | _back = null;
153 | _shouldBuildFront = false;
154 |
155 | if (_resolver != null) {
156 | // move previous loaded image to back & cancel any active loads.
157 | if (_resolver!.complete) _back = _fadeBack = _front;
158 | _resolver!.dispose();
159 | }
160 |
161 | // load the new image:
162 | _front = _sync = null;
163 | _resolver = image == null
164 | ? null
165 | : _ImageResolver(
166 | image,
167 | context,
168 | onError: _handleComplete,
169 | onComplete: _handleComplete,
170 | width: widget.width,
171 | height: widget.height,
172 | );
173 |
174 | // start transition to placeholder if there's no new image:
175 | if (_back != null && _resolver == null) _buildTransition();
176 | }
177 |
178 | void _handleComplete(_ImageResolver resolver) {
179 | _sync ??= true;
180 | // defer building the front content until build so we have an active context.
181 | setState(() => _shouldBuildFront = true);
182 | }
183 |
184 | void _buildFront(BuildContext context) {
185 | _shouldBuildFront = false;
186 | _ImageResolver resolver = _resolver!;
187 | _front = resolver.error
188 | ? widget.errorBuilder?.call(context, resolver.exception!)
189 | : _getImage(resolver.image);
190 | _buildTransition();
191 | }
192 |
193 | void _buildTransition() {
194 | final bool out = _front == null; // no new image
195 |
196 | // use the "fast" duration if sync load, error, or placeholder:
197 | bool fast = (_sync != false || _resolver?.error == true || out);
198 | Duration duration = (fast ? widget.syncDuration : null) ?? widget.duration;
199 |
200 | // Fade in for duration, out for 1/2 as long:
201 | _controller.duration = duration * (out ? 1 : 3 / 2);
202 |
203 | _fadeFront = _buildFade(
204 | child: _front,
205 | opacity: CurvedAnimation(
206 | parent: _controller,
207 | curve: Interval(0.0, 2 / 3, curve: widget.curve),
208 | ),
209 | );
210 |
211 | _fadeBack = _buildFade(
212 | child: _back,
213 | opacity: Tween(begin: 1.0, end: 0).animate(
214 | CurvedAnimation(
215 | parent: _controller,
216 | curve: Interval(out ? 0.0 : 2 / 3, 1.0),
217 | ),
218 | ),
219 | );
220 |
221 | if (_front != null || _back != null) _controller.forward(from: 0);
222 | }
223 |
224 | Widget? _buildFade({Widget? child, required Animation opacity}) {
225 | if (child == null) return null;
226 | // if the child is a loaded image, we can fade its opacity directly for better performance:
227 | return (child is RawImage)
228 | ? _getImage(child.image, opacity: opacity)
229 | : FadeTransition(opacity: opacity, child: child);
230 | }
231 |
232 | RawImage _getImage(ui.Image? image, {Animation? opacity}) {
233 | return RawImage(
234 | image: image,
235 | width: widget.width,
236 | height: widget.height,
237 | scale: widget.scale,
238 | fit: widget.fit,
239 | alignment: widget.alignment,
240 | repeat: widget.repeat,
241 | matchTextDirection: widget.matchTextDirection,
242 | opacity: opacity,
243 | );
244 | }
245 |
246 | @override
247 | Widget build(BuildContext context) {
248 | _sync ??= false;
249 | if (_shouldBuildFront) _buildFront(context);
250 | Widget? front = _fadeFront, back = _fadeBack;
251 |
252 | bool inLoad = _resolver != null && !_resolver!.complete;
253 | if (inLoad && widget.loadingBuilder != null) {
254 | _ImageResolver resolver = _resolver!;
255 | front = AnimatedBuilder(
256 | animation: resolver.notifier,
257 | builder: (_, __) => widget.loadingBuilder!(
258 | context,
259 | resolver.notifier.value,
260 | resolver.chunkEvent,
261 | ),
262 | );
263 | }
264 |
265 | List kids = [];
266 | if (widget.placeholder != null) kids.add(widget.placeholder!);
267 | if (back != null) kids.add(back);
268 | if (front != null) kids.add(front);
269 |
270 | Widget content = SizedBox(
271 | width: widget.width,
272 | height: widget.height,
273 | child: kids.isEmpty
274 | ? null
275 | : Stack(fit: StackFit.passthrough, children: kids),
276 | );
277 |
278 | if (widget.excludeFromSemantics) return content;
279 |
280 | String? label = widget.semanticLabel;
281 | return Semantics(
282 | container: label != null,
283 | image: true,
284 | label: label ?? "",
285 | child: content,
286 | );
287 | }
288 |
289 | @override
290 | void dispose() {
291 | _resolver?.dispose();
292 | _controller.dispose();
293 | super.dispose();
294 | }
295 | }
296 |
297 | // Simplifies working with image loading events and states.
298 | class _ImageResolver {
299 | _ImageResolver(
300 | ImageProvider provider,
301 | BuildContext context, {
302 | required this.onComplete,
303 | required this.onError,
304 | double? width,
305 | double? height,
306 | }) {
307 | Size? size = width != null && height != null ? Size(width, height) : null;
308 | ImageConfiguration config =
309 | createLocalImageConfiguration(context, size: size);
310 | _listener = ImageStreamListener(_handleComplete,
311 | onChunk: _handleProgress, onError: _handleError);
312 | _stream = provider.resolve(config);
313 | _stream.addListener(_listener); // Called sync if already completed.
314 | notifier = ValueNotifier(0);
315 | }
316 |
317 | Object? exception;
318 | ImageChunkEvent? chunkEvent;
319 | late final ValueNotifier notifier;
320 |
321 | final Function(_ImageResolver resolver) onComplete;
322 | final Function(_ImageResolver resolver) onError;
323 |
324 | late final ImageStream _stream;
325 | late final ImageStreamListener _listener;
326 | ImageInfo? _imageInfo;
327 | bool _complete = false;
328 |
329 | ui.Image? get image => _imageInfo?.image;
330 |
331 | bool get complete => _complete;
332 |
333 | bool get error => exception != null;
334 |
335 | void _handleComplete(ImageInfo imageInfo, bool sync) {
336 | _imageInfo = imageInfo;
337 | _complete = true;
338 | onComplete(this);
339 | }
340 |
341 | void _handleProgress(ImageChunkEvent event) {
342 | chunkEvent = event;
343 | notifier.value = event.expectedTotalBytes != null
344 | ? event.cumulativeBytesLoaded / event.expectedTotalBytes!
345 | : 0.0;
346 | }
347 |
348 | void _handleError(Object exc, StackTrace? _) {
349 | exception = exc;
350 | _complete = true;
351 | onError(this);
352 | }
353 |
354 | void dispose() {
355 | _stream.removeListener(_listener);
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/lib/src/widgets/styled_close_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/foundation.dart';
3 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
4 |
5 | class StyledCloseButton extends StatelessWidget {
6 | const StyledCloseButton({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return IconButton(
11 | onPressed: () {
12 | if (kIsWeb && !Navigator.of(context).canPop()) {
13 | Navigator.of(context, rootNavigator: true).pop();
14 | } else {
15 | Navigator.of(context).pop();
16 | }
17 | },
18 | icon: Icon(
19 | Icons.close,
20 | color: context.textColor,
21 | ),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/src/widgets/styled_text_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
4 |
5 | class StyledTextField extends StatelessWidget {
6 | final String? label;
7 | final String? hintText;
8 | final int minLines;
9 | final bool autoFocus;
10 | final String? helperText;
11 | final int? maxLength;
12 | final TextEditingController controller;
13 |
14 | const StyledTextField({
15 | super.key,
16 | required this.controller,
17 | this.label,
18 | this.hintText,
19 | this.autoFocus = false,
20 | this.minLines = 1,
21 | this.helperText,
22 | this.maxLength,
23 | });
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return TextField(
28 | minLines: minLines,
29 | maxLines: minLines,
30 | autofocus: autoFocus,
31 | maxLength: maxLength,
32 | controller: controller,
33 | textCapitalization: TextCapitalization.sentences,
34 | inputFormatters: const [
35 | CapitalizeFirstLetterFormatter(),
36 | ],
37 | style: TextStyle(
38 | fontSize: 18.0,
39 | color: context.textColor,
40 | ),
41 | cursorColor: context.textColor,
42 | decoration: InputDecoration(
43 | hintText: hintText,
44 | border: const OutlineInputBorder(
45 | borderSide: BorderSide.none,
46 | ),
47 | focusedBorder: const OutlineInputBorder(
48 | borderSide: BorderSide.none,
49 | ),
50 | enabledBorder: const OutlineInputBorder(
51 | borderSide: BorderSide.none,
52 | ),
53 | labelText: label,
54 | hintStyle: TextStyle(
55 | fontSize: 18.0,
56 | fontWeight: FontWeight.w400,
57 | color: context.secondaryTextColor,
58 | ),
59 | labelStyle: TextStyle(
60 | fontSize: 14.0,
61 | color: context.textColor,
62 | ),
63 | ),
64 | );
65 | }
66 | }
67 |
68 | class CapitalizeFirstLetterFormatter extends TextInputFormatter {
69 | const CapitalizeFirstLetterFormatter();
70 |
71 | @override
72 | TextEditingValue formatEditUpdate(
73 | TextEditingValue oldValue,
74 | TextEditingValue newValue,
75 | ) {
76 | if (newValue.text.isEmpty) {
77 | return newValue;
78 | }
79 |
80 | String newText =
81 | newValue.text[0].toUpperCase() + newValue.text.substring(1);
82 | return newValue.copyWith(
83 | text: newText,
84 | selection: newValue.selection,
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/src/widgets/tip_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_svg/flutter_svg.dart';
3 | import 'package:userorient_flutter/src/logic/l10n.dart';
4 |
5 | class TipCard extends StatelessWidget {
6 | const TipCard({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return AnimatedSize(
11 | duration: kThemeAnimationDuration,
12 | curve: Curves.easeInOut,
13 | child: Container(
14 | padding: const EdgeInsets.all(16.0),
15 | decoration: BoxDecoration(
16 | color: const Color(0xff529BDF).withOpacity(.1),
17 | borderRadius: BorderRadius.circular(16.0),
18 | ),
19 | child: Row(
20 | children: [
21 | SvgPicture.asset(
22 | 'assets/light-bulb.svg',
23 | package: 'userorient_flutter',
24 | ),
25 | const SizedBox(width: 12.0),
26 | Expanded(
27 | child: Text(
28 | L10n.tip,
29 | style: const TextStyle(
30 | fontSize: 14.0,
31 | height: 20 / 14,
32 | color: Color(0xff529BDF),
33 | ),
34 | ),
35 | ),
36 | ],
37 | ),
38 | ),
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/src/widgets/watermark.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_svg/flutter_svg.dart';
4 | import 'package:url_launcher/url_launcher.dart';
5 | import 'package:userorient_flutter/src/logic/l10n.dart';
6 | import 'package:userorient_flutter/src/utilities/build_context_extensions.dart';
7 | import 'package:userorient_flutter/src/widgets/bottom_padding.dart';
8 | import 'package:userorient_flutter/src/widgets/button.dart';
9 | import 'package:userorient_flutter/userorient_flutter.dart';
10 |
11 | // TODO: add license for not removing Watermark manually
12 | class Watermark extends StatelessWidget {
13 | const Watermark({super.key});
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return GestureDetector(
18 | onTap: () {
19 | launchUrl(
20 | Uri.parse('https://userorient.com'),
21 | mode: LaunchMode.externalApplication,
22 | );
23 | },
24 | child: Container(
25 | padding: EdgeInsets.only(
26 | top: 16.0,
27 | bottom: defaultTargetPlatform != TargetPlatform.iOS
28 | ? MediaQuery.of(context).padding.bottom + 12.0
29 | : MediaQuery.of(context).padding.bottom,
30 | ),
31 | decoration: BoxDecoration(
32 | boxShadow: const [
33 | BoxShadow(
34 | color: Colors.black12,
35 | offset: Offset(0, 0),
36 | blurRadius: 120.0,
37 | ),
38 | ],
39 | color: context.backgroundColor,
40 | border: Border(
41 | top: BorderSide(
42 | color: context.borderColor,
43 | width: 1.0,
44 | ),
45 | ),
46 | ),
47 | child: Column(
48 | children: [
49 | Button(
50 | onPressed: () {
51 | UserOrient.openForm(context);
52 | },
53 | icon: SvgPicture.asset(
54 | 'assets/add.svg',
55 | package: 'userorient_flutter',
56 | colorFilter: ColorFilter.mode(
57 | context.buttonTextColor,
58 | BlendMode.srcIn,
59 | ),
60 | ),
61 | label: L10n.addFeature,
62 | ),
63 | const SizedBox(height: 16.0),
64 | Row(
65 | mainAxisAlignment: MainAxisAlignment.center,
66 | children: [
67 | SvgPicture.asset(
68 | 'assets/uo${context.isDark ? '-dark' : ''}.svg',
69 | package: 'userorient_flutter',
70 | ),
71 | const SizedBox(width: 4.0),
72 | const Text(
73 | 'Powered by UserOrient',
74 | style: TextStyle(
75 | fontSize: 12.0,
76 | height: 16 / 12,
77 | color: Color(0xffACAEAF),
78 | ),
79 | ),
80 | // const SizedBox(width: 4.0),
81 | // const Text(
82 | // 'UserOrient',
83 | // style: TextStyle(
84 | // fontSize: 12.0,
85 | // height: 16 / 12,
86 | // color: Color(0xffACAEAF),
87 | // fontWeight: FontWeight.bold,
88 | // ),
89 | // ),
90 | ],
91 | ),
92 | if (defaultTargetPlatform == TargetPlatform.iOS)
93 | const BottomPadding(32),
94 | ],
95 | ),
96 | ),
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/lib/userorient_flutter.dart:
--------------------------------------------------------------------------------
1 | library userorient_flutter;
2 |
3 | export 'src/logic/user_orient.dart';
4 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: userorient_flutter
2 | description: "Feature Voting Board for Flutter. Collect feedback from your users and prioritize your development roadmap."
3 | version: 1.1.0
4 | homepage: https://userorient.com
5 | repository: https://github.com/userorient/userorient-flutter
6 | issue_tracker: https://github.com/userorient/userorient-flutter/issues
7 | topics:
8 | - feature-request
9 | - feedback
10 | - user-experience
11 | - board
12 | - upvote
13 |
14 | environment:
15 | sdk: ">=2.17.0 <4.0.0"
16 | flutter: ">=1.17.0"
17 |
18 | dependencies:
19 | flutter:
20 | sdk: flutter
21 | flutter_svg: ^2.0.10+1
22 | flutter_svg_provider: ^1.0.7
23 | http: ^1.2.1
24 | shared_preferences: ^2.2.3
25 | url_launcher: ^6.3.1
26 | url_launcher_ios: ^6.3.2
27 |
28 | dev_dependencies:
29 | flutter_test:
30 | sdk: flutter
31 | flutter_lints: ^3.0.0
32 |
33 | flutter:
34 | assets:
35 | - assets/
--------------------------------------------------------------------------------
/test/userorient_flutter_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | void main() {
4 | test('', () {});
5 | }
6 |
--------------------------------------------------------------------------------