commit dfc73ac19f5000ea2b66dd2374529cf7807f416a Author: fengshengxiong Date: Fri Jan 16 18:22:32 2026 +0800 AtmoSphere:Weather 1.1版本 接入ironSource diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5d5b44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ +about.txt +lib.zip +截图 +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..9e8b9e9 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + - platform: ios + create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..295bf2a --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# atmo_sphere + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/assets/images/stonicons.png b/assets/images/stonicons.png new file mode 100644 index 0000000..8810222 Binary files /dev/null and b/assets/images/stonicons.png differ diff --git a/assets/wallpapers/clear_day/image1.png b/assets/wallpapers/clear_day/image1.png new file mode 100644 index 0000000..5b66409 Binary files /dev/null and b/assets/wallpapers/clear_day/image1.png differ diff --git a/assets/wallpapers/clear_day/image2.png b/assets/wallpapers/clear_day/image2.png new file mode 100644 index 0000000..8666b83 Binary files /dev/null and b/assets/wallpapers/clear_day/image2.png differ diff --git a/assets/wallpapers/clear_day/image3.png b/assets/wallpapers/clear_day/image3.png new file mode 100644 index 0000000..6802538 Binary files /dev/null and b/assets/wallpapers/clear_day/image3.png differ diff --git a/assets/wallpapers/clear_night/image1.png b/assets/wallpapers/clear_night/image1.png new file mode 100644 index 0000000..31b3f1a Binary files /dev/null and b/assets/wallpapers/clear_night/image1.png differ diff --git a/assets/wallpapers/clear_night/image2.png b/assets/wallpapers/clear_night/image2.png new file mode 100644 index 0000000..6f77420 Binary files /dev/null and b/assets/wallpapers/clear_night/image2.png differ diff --git a/assets/wallpapers/clear_night/image3.png b/assets/wallpapers/clear_night/image3.png new file mode 100644 index 0000000..1956abc Binary files /dev/null and b/assets/wallpapers/clear_night/image3.png differ diff --git a/assets/wallpapers/clear_night/image4.png b/assets/wallpapers/clear_night/image4.png new file mode 100644 index 0000000..5aa5c47 Binary files /dev/null and b/assets/wallpapers/clear_night/image4.png differ diff --git a/assets/wallpapers/clear_night/image5.png b/assets/wallpapers/clear_night/image5.png new file mode 100644 index 0000000..3d27810 Binary files /dev/null and b/assets/wallpapers/clear_night/image5.png differ diff --git a/assets/wallpapers/cloudy_day/image1.png b/assets/wallpapers/cloudy_day/image1.png new file mode 100644 index 0000000..96a5222 Binary files /dev/null and b/assets/wallpapers/cloudy_day/image1.png differ diff --git a/assets/wallpapers/cloudy_day/image2.png b/assets/wallpapers/cloudy_day/image2.png new file mode 100644 index 0000000..d85595a Binary files /dev/null and b/assets/wallpapers/cloudy_day/image2.png differ diff --git a/assets/wallpapers/cloudy_day/image3.png b/assets/wallpapers/cloudy_day/image3.png new file mode 100644 index 0000000..0203882 Binary files /dev/null and b/assets/wallpapers/cloudy_day/image3.png differ diff --git a/assets/wallpapers/cloudy_day/image4.png b/assets/wallpapers/cloudy_day/image4.png new file mode 100644 index 0000000..30d5855 Binary files /dev/null and b/assets/wallpapers/cloudy_day/image4.png differ diff --git a/assets/wallpapers/cloudy_night/image1.png b/assets/wallpapers/cloudy_night/image1.png new file mode 100644 index 0000000..f136f3f Binary files /dev/null and b/assets/wallpapers/cloudy_night/image1.png differ diff --git a/assets/wallpapers/cloudy_night/image2.png b/assets/wallpapers/cloudy_night/image2.png new file mode 100644 index 0000000..23be671 Binary files /dev/null and b/assets/wallpapers/cloudy_night/image2.png differ diff --git a/assets/wallpapers/cloudy_night/image3.png b/assets/wallpapers/cloudy_night/image3.png new file mode 100644 index 0000000..3aeec89 Binary files /dev/null and b/assets/wallpapers/cloudy_night/image3.png differ diff --git a/assets/wallpapers/cloudy_night/image4.png b/assets/wallpapers/cloudy_night/image4.png new file mode 100644 index 0000000..16454e9 Binary files /dev/null and b/assets/wallpapers/cloudy_night/image4.png differ diff --git a/assets/wallpapers/default/image1.png b/assets/wallpapers/default/image1.png new file mode 100644 index 0000000..eca2c55 Binary files /dev/null and b/assets/wallpapers/default/image1.png differ diff --git a/assets/wallpapers/fog_day/image1.png b/assets/wallpapers/fog_day/image1.png new file mode 100644 index 0000000..575f12e Binary files /dev/null and b/assets/wallpapers/fog_day/image1.png differ diff --git a/assets/wallpapers/fog_day/image2.png b/assets/wallpapers/fog_day/image2.png new file mode 100644 index 0000000..831ecd8 Binary files /dev/null and b/assets/wallpapers/fog_day/image2.png differ diff --git a/assets/wallpapers/fog_day/image3.png b/assets/wallpapers/fog_day/image3.png new file mode 100644 index 0000000..290b3a6 Binary files /dev/null and b/assets/wallpapers/fog_day/image3.png differ diff --git a/assets/wallpapers/fog_night/image1.png b/assets/wallpapers/fog_night/image1.png new file mode 100644 index 0000000..864c269 Binary files /dev/null and b/assets/wallpapers/fog_night/image1.png differ diff --git a/assets/wallpapers/fog_night/image2.png b/assets/wallpapers/fog_night/image2.png new file mode 100644 index 0000000..6b41c43 Binary files /dev/null and b/assets/wallpapers/fog_night/image2.png differ diff --git a/assets/wallpapers/fog_night/image3.png b/assets/wallpapers/fog_night/image3.png new file mode 100644 index 0000000..bc43617 Binary files /dev/null and b/assets/wallpapers/fog_night/image3.png differ diff --git a/assets/wallpapers/fog_night/image4.png b/assets/wallpapers/fog_night/image4.png new file mode 100644 index 0000000..9f58e54 Binary files /dev/null and b/assets/wallpapers/fog_night/image4.png differ diff --git a/assets/wallpapers/rain_day/image1.png b/assets/wallpapers/rain_day/image1.png new file mode 100644 index 0000000..9c2d269 Binary files /dev/null and b/assets/wallpapers/rain_day/image1.png differ diff --git a/assets/wallpapers/rain_day/image2.png b/assets/wallpapers/rain_day/image2.png new file mode 100644 index 0000000..f8dda42 Binary files /dev/null and b/assets/wallpapers/rain_day/image2.png differ diff --git a/assets/wallpapers/rain_day/image3.png b/assets/wallpapers/rain_day/image3.png new file mode 100644 index 0000000..9c9d579 Binary files /dev/null and b/assets/wallpapers/rain_day/image3.png differ diff --git a/assets/wallpapers/rain_day/image4.png b/assets/wallpapers/rain_day/image4.png new file mode 100644 index 0000000..f5192ce Binary files /dev/null and b/assets/wallpapers/rain_day/image4.png differ diff --git a/assets/wallpapers/rain_night/image1.png b/assets/wallpapers/rain_night/image1.png new file mode 100644 index 0000000..735b96a Binary files /dev/null and b/assets/wallpapers/rain_night/image1.png differ diff --git a/assets/wallpapers/rain_night/image2.png b/assets/wallpapers/rain_night/image2.png new file mode 100644 index 0000000..59cf2c1 Binary files /dev/null and b/assets/wallpapers/rain_night/image2.png differ diff --git a/assets/wallpapers/rain_night/image3.png b/assets/wallpapers/rain_night/image3.png new file mode 100644 index 0000000..0bae295 Binary files /dev/null and b/assets/wallpapers/rain_night/image3.png differ diff --git a/assets/wallpapers/rain_night/image4.png b/assets/wallpapers/rain_night/image4.png new file mode 100644 index 0000000..1f24560 Binary files /dev/null and b/assets/wallpapers/rain_night/image4.png differ diff --git a/assets/wallpapers/snow_day/image1.png b/assets/wallpapers/snow_day/image1.png new file mode 100644 index 0000000..db1e924 Binary files /dev/null and b/assets/wallpapers/snow_day/image1.png differ diff --git a/assets/wallpapers/snow_day/image2.png b/assets/wallpapers/snow_day/image2.png new file mode 100644 index 0000000..55dc50d Binary files /dev/null and b/assets/wallpapers/snow_day/image2.png differ diff --git a/assets/wallpapers/snow_day/image3.png b/assets/wallpapers/snow_day/image3.png new file mode 100644 index 0000000..9fdc089 Binary files /dev/null and b/assets/wallpapers/snow_day/image3.png differ diff --git a/assets/wallpapers/snow_day/image4.png b/assets/wallpapers/snow_day/image4.png new file mode 100644 index 0000000..4ff8fe3 Binary files /dev/null and b/assets/wallpapers/snow_day/image4.png differ diff --git a/assets/wallpapers/snow_night/imaeg1.png b/assets/wallpapers/snow_night/imaeg1.png new file mode 100644 index 0000000..9d700b4 Binary files /dev/null and b/assets/wallpapers/snow_night/imaeg1.png differ diff --git a/assets/wallpapers/snow_night/imaeg2.png b/assets/wallpapers/snow_night/imaeg2.png new file mode 100644 index 0000000..b3d6e25 Binary files /dev/null and b/assets/wallpapers/snow_night/imaeg2.png differ diff --git a/assets/wallpapers/snow_night/imaeg3.png b/assets/wallpapers/snow_night/imaeg3.png new file mode 100644 index 0000000..c26d26e Binary files /dev/null and b/assets/wallpapers/snow_night/imaeg3.png differ diff --git a/assets/wallpapers/snow_night/imaeg4.png b/assets/wallpapers/snow_night/imaeg4.png new file mode 100644 index 0000000..1aadd62 Binary files /dev/null and b/assets/wallpapers/snow_night/imaeg4.png differ diff --git a/assets/wallpapers/snow_night/imaeg5.png b/assets/wallpapers/snow_night/imaeg5.png new file mode 100644 index 0000000..73d5cfd Binary files /dev/null and b/assets/wallpapers/snow_night/imaeg5.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..c1d834f --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '15.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! :linkage => :static + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..0fa8f3a --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,363 @@ +PODS: + - Ads-Global (7.8.0.4): + - Ads-Global/BUAdSDK (= 7.8.0.4) + - Ads-Global/BUAdSDK (7.8.0.4): + - Ads-Global/PangleSDK + - Ads-Global/TikTokBusinessSDK + - Ads-Global/PangleSDK (7.8.0.4) + - Ads-Global/TikTokBusinessSDK (7.8.0.4) + - app_tracking_transparency (0.0.1): + - Flutter + - BigoADS (5.0.0) + - ChartboostSDK (9.10.1) + - Firebase/CoreOnly (12.6.0): + - FirebaseCore (~> 12.6.0) + - Firebase/Crashlytics (12.6.0): + - Firebase/CoreOnly + - FirebaseCrashlytics (~> 12.6.0) + - firebase_analytics (12.1.0): + - firebase_core + - FirebaseAnalytics (= 12.6.0) + - Flutter + - firebase_core (4.3.0): + - Firebase/CoreOnly (= 12.6.0) + - Flutter + - firebase_crashlytics (5.0.6): + - Firebase/Crashlytics (= 12.6.0) + - firebase_core + - Flutter + - FirebaseAnalytics (12.6.0): + - FirebaseAnalytics/Default (= 12.6.0) + - FirebaseCore (~> 12.6.0) + - FirebaseInstallations (~> 12.6.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (12.6.0): + - FirebaseCore (~> 12.6.0) + - FirebaseInstallations (~> 12.6.0) + - GoogleAppMeasurement/Default (= 12.6.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseCore (12.6.0): + - FirebaseCoreInternal (~> 12.6.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreExtension (12.6.0): + - FirebaseCore (~> 12.6.0) + - FirebaseCoreInternal (12.6.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseCrashlytics (12.6.0): + - FirebaseCore (~> 12.6.0) + - FirebaseInstallations (~> 12.6.0) + - FirebaseRemoteConfigInterop (~> 12.6.0) + - FirebaseSessions (~> 12.6.0) + - GoogleDataTransport (~> 10.1) + - GoogleUtilities/Environment (~> 8.1) + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - FirebaseInstallations (12.6.0): + - FirebaseCore (~> 12.6.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - PromisesObjC (~> 2.4) + - FirebaseRemoteConfigInterop (12.6.0) + - FirebaseSessions (12.6.0): + - FirebaseCore (~> 12.6.0) + - FirebaseCoreExtension (~> 12.6.0) + - FirebaseInstallations (~> 12.6.0) + - GoogleDataTransport (~> 10.1) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - nanopb (~> 3.30910.0) + - PromisesSwift (~> 2.1) + - Flutter (1.0.0) + - Fyber_Marketplace_SDK (8.4.2) + - geolocator_apple (1.2.0): + - Flutter + - FlutterMacOS + - GoogleAdsOnDeviceConversion (3.2.0): + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Core (12.6.0): + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Default (12.6.0): + - GoogleAdsOnDeviceConversion (~> 3.2.0) + - GoogleAppMeasurement/Core (= 12.6.0) + - GoogleAppMeasurement/IdentitySupport (= 12.6.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/IdentitySupport (12.6.0): + - GoogleAppMeasurement/Core (= 12.6.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - InMobiSDK (11.1.0) + - IronSourceAdQualitySDK (9.1.1) + - IronSourceBigoAdapter (5.1.0.0): + - BigoADS (= 5.0.0) + - IronSourceSDK/Ads (~> 9.0) + - IronSourceChartboostAdapter (5.2.0.0): + - ChartboostSDK (= 9.10.1) + - IronSourceSDK/Ads (~> 9.0) + - IronSourceFyberAdapter (5.3.0.0): + - Fyber_Marketplace_SDK (= 8.4.2) + - IronSourceSDK/Ads (~> 9.0) + - IronSourceInMobiAdapter (5.3.0.0): + - InMobiSDK (= 11.1.0) + - IronSourceSDK/Ads (~> 9.0) + - IronSourceMintegralAdapter (5.3.0.0): + - IronSourceSDK/Ads (~> 9.0) + - MintegralAdSDK (= 8.0.3) + - IronSourcePangleAdapter (5.7.0.0): + - Ads-Global (= 7.8.0.4) + - IronSourceSDK/Ads (~> 9.0) + - IronSourceSDK (9.2.0.0): + - IronSourceSDK/AdQuality (= 9.2.0.0) + - IronSourceSDK/Ads (= 9.2.0.0) + - IronSourceSDK/AdQuality (9.2.0.0): + - IronSourceAdQualitySDK (~> 9.1.1) + - IronSourceSDK/Ads (9.2.0.0) + - IronSourceUnityAdsAdapter (5.3.0.0): + - IronSourceSDK/Ads (~> 9.0) + - UnityAds (= 4.16.4) + - IronSourceVungleAdapter (5.3.0.0): + - IronSourceSDK/Ads (~> 9.0) + - VungleAds (= 7.6.2) + - kk_device_infos (0.0.1): + - Flutter + - MintegralAdSDK (8.0.3): + - MintegralAdSDK/BannerAd (= 8.0.3) + - MintegralAdSDK/BidBannerAd (= 8.0.3) + - MintegralAdSDK/BidInterstitialVideoAd (= 8.0.3) + - MintegralAdSDK/BidNativeAd (= 8.0.3) + - MintegralAdSDK/BidNewInterstitialAd (= 8.0.3) + - MintegralAdSDK/BidRewardVideoAd (= 8.0.3) + - MintegralAdSDK/InterstitialVideoAd (= 8.0.3) + - MintegralAdSDK/NativeAd (= 8.0.3) + - MintegralAdSDK/NewInterstitialAd (= 8.0.3) + - MintegralAdSDK/RewardVideoAd (= 8.0.3) + - MintegralAdSDK/BannerAd (8.0.3): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/BidBannerAd (8.0.3): + - MintegralAdSDK/BannerAd + - MintegralAdSDK/BidNativeAd + - MintegralAdSDK/BidInterstitialVideoAd (8.0.3): + - MintegralAdSDK/BidNativeAd + - MintegralAdSDK/InterstitialVideoAd + - MintegralAdSDK/BidNativeAd (8.0.3): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/BidNewInterstitialAd (8.0.3): + - MintegralAdSDK/BidNativeAd + - MintegralAdSDK/NewInterstitialAd + - MintegralAdSDK/BidRewardVideoAd (8.0.3): + - MintegralAdSDK/BidNativeAd + - MintegralAdSDK/RewardVideoAd + - MintegralAdSDK/InterstitialVideoAd (8.0.3): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/NativeAd (8.0.3) + - MintegralAdSDK/NewInterstitialAd (8.0.3): + - MintegralAdSDK/InterstitialVideoAd + - MintegralAdSDK/NativeAd + - MintegralAdSDK/RewardVideoAd (8.0.3): + - MintegralAdSDK/NativeAd + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) + - share_plus (0.0.1): + - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - unity_levelplay_mediation (9.0.1): + - Flutter + - IronSourceBigoAdapter (= 5.1.0.0) + - IronSourceChartboostAdapter (= 5.2.0.0) + - IronSourceFyberAdapter (= 5.3.0.0) + - IronSourceInMobiAdapter (= 5.3.0.0) + - IronSourceMintegralAdapter (= 5.3.0.0) + - IronSourcePangleAdapter (= 5.7.0.0) + - IronSourceSDK (= 9.2.0.0) + - IronSourceUnityAdsAdapter (= 5.3.0.0) + - IronSourceVungleAdapter (= 5.3.0.0) + - UnityAds (4.16.4) + - VungleAds (7.6.2) + +DEPENDENCIES: + - app_tracking_transparency (from `.symlinks/plugins/app_tracking_transparency/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) + - Flutter (from `Flutter`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) + - kk_device_infos (from `.symlinks/plugins/kk_device_infos/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - unity_levelplay_mediation (from `.symlinks/plugins/unity_levelplay_mediation/ios`) + +SPEC REPOS: + trunk: + - Ads-Global + - BigoADS + - ChartboostSDK + - Firebase + - FirebaseAnalytics + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseCrashlytics + - FirebaseInstallations + - FirebaseRemoteConfigInterop + - FirebaseSessions + - Fyber_Marketplace_SDK + - GoogleAdsOnDeviceConversion + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleUtilities + - InMobiSDK + - IronSourceAdQualitySDK + - IronSourceBigoAdapter + - IronSourceChartboostAdapter + - IronSourceFyberAdapter + - IronSourceInMobiAdapter + - IronSourceMintegralAdapter + - IronSourcePangleAdapter + - IronSourceSDK + - IronSourceUnityAdsAdapter + - IronSourceVungleAdapter + - MintegralAdSDK + - nanopb + - PromisesObjC + - PromisesSwift + - UnityAds + - VungleAds + +EXTERNAL SOURCES: + app_tracking_transparency: + :path: ".symlinks/plugins/app_tracking_transparency/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_crashlytics: + :path: ".symlinks/plugins/firebase_crashlytics/ios" + Flutter: + :path: Flutter + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/darwin" + kk_device_infos: + :path: ".symlinks/plugins/kk_device_infos/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + unity_levelplay_mediation: + :path: ".symlinks/plugins/unity_levelplay_mediation/ios" + +SPEC CHECKSUMS: + Ads-Global: beee5fad34d7ccc41441aff5853113efdf46f8e6 + app_tracking_transparency: 3d84f147f67ca82d3c15355c36b1fa6b66ca7c92 + BigoADS: 789ca51394adb1adb405e7e01ec8e43d3dc14f15 + ChartboostSDK: 377d7bbec1cccba0f7119f5d0830bd1ada89b672 + Firebase: a451a7b61536298fd5cbfe3a746fd40443a50679 + firebase_analytics: 4f9cca09e65f6c2944a862c6dc86f6bed9fb769c + firebase_core: ba00a168e719694f38960502ceb560285603d073 + firebase_crashlytics: 13f4b77e9ce2a84b1f8ea07f293db5b6213ce1cf + FirebaseAnalytics: d0a97a0db6425e5a5d966340b87f92ca7b13a557 + FirebaseCore: 0e38ad5d62d980a47a64b8e9301ffa311457be04 + FirebaseCoreExtension: 032fd6f8509e591fda8cb76f6651f20d926b121f + FirebaseCoreInternal: 69bf1306a05b8ac43004f6cc1f804bb7b05b229e + FirebaseCrashlytics: 3d6248c50726ee7832aef0e53cb84c9e64d9fa7e + FirebaseInstallations: 631b38da2e11a83daa4bfb482f79d286a5dfa7ad + FirebaseRemoteConfigInterop: 3443b8cb8fffd76bb3e03b2a84bfd3db952fcda4 + FirebaseSessions: 2e8f808347e665dff3e5843f275715f07045297d + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + Fyber_Marketplace_SDK: 47267f7f78339628a159ceb30392f03d62a52271 + geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e + GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f + GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + InMobiSDK: 43b1d6560bd75c8c382bb54fa8e248f5ed979860 + IronSourceAdQualitySDK: 56314ade811a5226f76dfd377bc09aca0d6610c0 + IronSourceBigoAdapter: c6ef50e6fab98deee95cde4e44af017c20334e1b + IronSourceChartboostAdapter: ee6386aaa7f6e20f3d429600c0856ad3e1de1d34 + IronSourceFyberAdapter: 38126581d14f909732cd10e0708001940b4cd861 + IronSourceInMobiAdapter: 55e5a9514ca081c56cc87567a548ce6d043e39bb + IronSourceMintegralAdapter: 591aab4b759f08aee77b8dd32b45783b7df3b5da + IronSourcePangleAdapter: 173b3eee37078507aab2dbabcb165dbd1c104205 + IronSourceSDK: 66e1483cd62fb63162a965651f6f41c6b91664db + IronSourceUnityAdsAdapter: e159ed615130e7623b2f9802bfd612fa146984e4 + IronSourceVungleAdapter: b7337ccbd9f22dd69b65f0b3c4f9bd93b84c79eb + kk_device_infos: 44a194dc1105227cc5c904a1b24ac375f3506870 + MintegralAdSDK: ee093ad2ae9498c8724e4cfffce85f51c780dab6 + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + unity_levelplay_mediation: d7940dc4340036ff5048f2336a4cc3f846c6ae47 + UnityAds: 91a5d786c1e79fcbf702c525af4700158aeb36c8 + VungleAds: f0edfe120c864561e3da762a06529edd26b07eef + +PODFILE CHECKSUM: 263a3e327ec8c049d1083c4cdd9efcd73625e571 + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..91e497b --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,785 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 232916E69CEA98AA3C5D8FC2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68BB737E926AA1E332592D4C /* Pods_Runner.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + D8D5AA632F0E5AB3008C096F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D8D5AA622F0E5AB3008C096F /* GoogleService-Info.plist */; }; + EF57E7E68A40E5A60F12B8AC /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E652C672BFC43BA2B2E9AE42 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 271E5527CD963BD29F03D5C1 /* 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 = ""; }; + 279D813136A1240AF24AEA46 /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 678ED081AED0797D7C339604 /* 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 = ""; }; + 68BB737E926AA1E332592D4C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 69DCEBBA612FC9FBBF8ECA67 /* 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8D47FE8BF9504FE84D7A28C2 /* 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 = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B9084720B55E65B6E7ACCF4B /* 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 = ""; }; + D8D5AA622F0E5AB3008C096F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + E652C672BFC43BA2B2E9AE42 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3F073F449DF9B892E1582D70 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EF57E7E68A40E5A60F12B8AC /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 232916E69CEA98AA3C5D8FC2 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00EF57822283CC1A4E504ED0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 68BB737E926AA1E332592D4C /* Pods_Runner.framework */, + E652C672BFC43BA2B2E9AE42 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + A8CAD8CADBE1D8B859CD7CFB /* Pods */, + 00EF57822283CC1A4E504ED0 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + D8D5AA622F0E5AB3008C096F /* GoogleService-Info.plist */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + A8CAD8CADBE1D8B859CD7CFB /* Pods */ = { + isa = PBXGroup; + children = ( + 271E5527CD963BD29F03D5C1 /* Pods-Runner.debug.xcconfig */, + B9084720B55E65B6E7ACCF4B /* Pods-Runner.release.xcconfig */, + 8D47FE8BF9504FE84D7A28C2 /* Pods-Runner.profile.xcconfig */, + 69DCEBBA612FC9FBBF8ECA67 /* Pods-RunnerTests.debug.xcconfig */, + 279D813136A1240AF24AEA46 /* Pods-RunnerTests.release.xcconfig */, + 678ED081AED0797D7C339604 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 69E0A5E0D0DB865DC491E784 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 3F073F449DF9B892E1582D70 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 5798BD68A0C490C498B0CA0B /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 98741C0D2F35CED0038FE368 /* [CP] Copy Pods Resources */, + 94CAC34616C4D9200AB384BC /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + preferredProjectObjectVersion = 77; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + D8D5AA632F0E5AB3008C096F /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 5798BD68A0C490C498B0CA0B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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"; + showEnvVarsInLog = 0; + }; + 69E0A5E0D0DB865DC491E784 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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"; + showEnvVarsInLog = 0; + }; + 94CAC34616C4D9200AB384BC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 98741C0D2F35CED0038FE368 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = H66T4YU58A; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.atmoSphereWeather.atmoSphereWeather; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = atmosphere; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 69DCEBBA612FC9FBBF8ECA67 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.atmoSphere.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 279D813136A1240AF24AEA46 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.atmoSphere.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 678ED081AED0797D7C339604 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.atmoSphere.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = H66T4YU58A; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.atmoSphereWeather.atmoSphereWeather; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = atmosphere; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = H66T4YU58A; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.atmoSphereWeather.atmoSphereWeather; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = atmosphere; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..fa4cdb6 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..c68df94 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,120 @@ +{ + "images": [ + { + "size": "20x20", + "idiom": "universal", + "filename": "icon-20@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "20x20", + "idiom": "universal", + "filename": "icon-20@3x.png", + "scale": "3x", + "platform": "ios" + }, + { + "size": "29x29", + "idiom": "universal", + "filename": "icon-29@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "29x29", + "idiom": "universal", + "filename": "icon-29@3x.png", + "scale": "3x", + "platform": "ios" + }, + { + "size": "38x38", + "idiom": "universal", + "filename": "icon-38@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "38x38", + "idiom": "universal", + "filename": "icon-38@3x.png", + "scale": "3x", + "platform": "ios" + }, + { + "size": "40x40", + "idiom": "universal", + "filename": "icon-40@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "40x40", + "idiom": "universal", + "filename": "icon-40@3x.png", + "scale": "3x", + "platform": "ios" + }, + { + "size": "60x60", + "idiom": "universal", + "filename": "icon-60@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "60x60", + "idiom": "universal", + "filename": "icon-60@3x.png", + "scale": "3x", + "platform": "ios" + }, + { + "size": "64x64", + "idiom": "universal", + "filename": "icon-64@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "64x64", + "idiom": "universal", + "filename": "icon-64@3x.png", + "scale": "3x", + "platform": "ios" + }, + { + "size": "68x68", + "idiom": "universal", + "filename": "icon-68@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "76x76", + "idiom": "universal", + "filename": "icon-76@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "83.5x83.5", + "idiom": "universal", + "filename": "icon-83.5@2x.png", + "scale": "2x", + "platform": "ios" + }, + { + "size": "1024x1024", + "idiom": "universal", + "filename": "icon-1024.png", + "scale": "1x", + "platform": "ios" + } + ], + "info": { + "version": 1, + "author": "icon.wuruihong.com" + } +} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 0000000..f562e8b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png new file mode 100644 index 0000000..34aa212 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png new file mode 100644 index 0000000..cc13981 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png new file mode 100644 index 0000000..eb3108a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png new file mode 100644 index 0000000..ccb631b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png new file mode 100644 index 0000000..be36add Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png new file mode 100644 index 0000000..8f33622 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png new file mode 100644 index 0000000..ffa198c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png new file mode 100644 index 0000000..3e46978 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png new file mode 100644 index 0000000..3e46978 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png new file mode 100644 index 0000000..f5b8e57 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png new file mode 100644 index 0000000..e1b808a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png new file mode 100644 index 0000000..e535abc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png new file mode 100644 index 0000000..e215660 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png new file mode 100644 index 0000000..e98eab3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png new file mode 100644 index 0000000..d7c5183 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..a317ab3 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyBJ9bnq3aCfZvl7yh1gRxdHAGAzKmEQeUI + GCM_SENDER_ID + 1096997940202 + PLIST_VERSION + 1 + BUNDLE_ID + com.atmoSphereWeather.atmoSphereWeather + PROJECT_ID + atmosphere-b59e0 + STORAGE_BUCKET + atmosphere-b59e0.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:1096997940202:ios:4be9f1a44c9b353309a093 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..6b9a570 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,853 @@ + + + + + NSLocationAlwaysAndWhenInUseUsageDescription + AtmoSphere needs access to your location to show weather information for your current position. + LSApplicationQueriesSchemes + + taobao + pinduoduo + openapp.jdmobile + imeituan + iosamap + alipay + baiduboxapp + vipshop + tmall + meituanwaimai + kwai + eleme + xhsdiscover + ksnebula + sinaweibo + fleamarket + id6443575749 + com.pwrd.zhuxian2.zs + tbopen + pddopen + baiduboxlite + wireless1688 + iqiyi + weixin + taobaotravel + alipays + youku + + NSUserTrackingUsageDescription + Obtain tagging permissions to provide you with better, safer, personalized services and content. We will not use it for other purposes without consent. Once enabled, you can also go to the system Settings-Privacy; to turn it off at any time. + SKAdNetworkItems + + + SKAdNetworkIdentifier + 22mmun2rn5.skadnetwork + + + SKAdNetworkIdentifier + 238da6jt44.skadnetwork + + + SKAdNetworkIdentifier + 24t9a8vw3c.skadnetwork + + + SKAdNetworkIdentifier + 24zw6aqk47.skadnetwork + + + SKAdNetworkIdentifier + 252b5q8x7y.skadnetwork + + + SKAdNetworkIdentifier + 275upjj5gd.skadnetwork + + + SKAdNetworkIdentifier + 294l99pt4k.skadnetwork + + + SKAdNetworkIdentifier + 2fnua5tdw4.skadnetwork + + + SKAdNetworkIdentifier + 2u9pt9hc89.skadnetwork + + + SKAdNetworkIdentifier + 32z4fx6l9h.skadnetwork + + + SKAdNetworkIdentifier + 33r6p7g8nc.skadnetwork + + + SKAdNetworkIdentifier + 3l6bd9hu43.skadnetwork + + + SKAdNetworkIdentifier + 3qcr597p9d.skadnetwork + + + SKAdNetworkIdentifier + 3qy4746246.skadnetwork + + + SKAdNetworkIdentifier + 3rd42ekr43.skadnetwork + + + SKAdNetworkIdentifier + 3sh42y64q3.skadnetwork + + + SKAdNetworkIdentifier + 424m5254lk.skadnetwork + + + SKAdNetworkIdentifier + 4468km3ulz.skadnetwork + + + SKAdNetworkIdentifier + 44jx6755aq.skadnetwork + + + SKAdNetworkIdentifier + 44n7hlldy6.skadnetwork + + + SKAdNetworkIdentifier + 47vhws6wlr.skadnetwork + + + SKAdNetworkIdentifier + 488r3q3dtq.skadnetwork + + + SKAdNetworkIdentifier + 4dzt52r2t5.skadnetwork + + + SKAdNetworkIdentifier + 4fzdc2evr5.skadnetwork + + + SKAdNetworkIdentifier + 4mn522wn87.skadnetwork + + + SKAdNetworkIdentifier + 4pfyvq9l8r.skadnetwork + + + SKAdNetworkIdentifier + 4w7y6s5ca2.skadnetwork + + + SKAdNetworkIdentifier + 523jb4fst2.skadnetwork + + + SKAdNetworkIdentifier + 52fl2v3hgk.skadnetwork + + + SKAdNetworkIdentifier + 54nzkqm89y.skadnetwork + + + SKAdNetworkIdentifier + 55644vm79v.skadnetwork + + + SKAdNetworkIdentifier + 55y65gfgn7.skadnetwork + + + SKAdNetworkIdentifier + 577p5t736z.skadnetwork + + + SKAdNetworkIdentifier + 578prtvx9j.skadnetwork + + + SKAdNetworkIdentifier + 5a6flpkh64.skadnetwork + + + SKAdNetworkIdentifier + 5f5u5tfb26.skadnetwork + + + SKAdNetworkIdentifier + 5l3tpt7t6e.skadnetwork + + + SKAdNetworkIdentifier + 5lm9lj6jb7.skadnetwork + + + SKAdNetworkIdentifier + 5tjdwbrq8w.skadnetwork + + + SKAdNetworkIdentifier + 6964rsfnh4.skadnetwork + + + SKAdNetworkIdentifier + 6g9af3uyq4.skadnetwork + + + SKAdNetworkIdentifier + 6p4ks3rnbw.skadnetwork + + + SKAdNetworkIdentifier + 6rd35atwn8.skadnetwork + + + SKAdNetworkIdentifier + 6v7lgmsu45.skadnetwork + + + SKAdNetworkIdentifier + 6xzpu9s2p8.skadnetwork + + + SKAdNetworkIdentifier + 6yxyv74ff7.skadnetwork + + + SKAdNetworkIdentifier + 737z793b9f.skadnetwork + + + SKAdNetworkIdentifier + 74b6s63p6l.skadnetwork + + + SKAdNetworkIdentifier + 7953jerfzd.skadnetwork + + + SKAdNetworkIdentifier + 79pbpufp6p.skadnetwork + + + SKAdNetworkIdentifier + 7bxrt786m8.skadnetwork + + + SKAdNetworkIdentifier + 7fbxrn65az.skadnetwork + + + SKAdNetworkIdentifier + 7fmhfwg9en.skadnetwork + + + SKAdNetworkIdentifier + 7rz58n8ntl.skadnetwork + + + SKAdNetworkIdentifier + 7ug5zh24hu.skadnetwork + + + SKAdNetworkIdentifier + 84993kbrcf.skadnetwork + + + SKAdNetworkIdentifier + 87u5trcl3r.skadnetwork + + + SKAdNetworkIdentifier + 89z7zv988g.skadnetwork + + + SKAdNetworkIdentifier + 8c4e2ghe7u.skadnetwork + + + SKAdNetworkIdentifier + 8m87ys6875.skadnetwork + + + SKAdNetworkIdentifier + 8r8llnkz5a.skadnetwork + + + SKAdNetworkIdentifier + 8s468mfl3y.skadnetwork + + + SKAdNetworkIdentifier + 97r2b46745.skadnetwork + + + SKAdNetworkIdentifier + 9b89h5y424.skadnetwork + + + SKAdNetworkIdentifier + 9g2aggbj52.skadnetwork + + + SKAdNetworkIdentifier + 9nlqeag3gk.skadnetwork + + + SKAdNetworkIdentifier + 9rd848q2bz.skadnetwork + + + SKAdNetworkIdentifier + 9t245vhmpl.skadnetwork + + + SKAdNetworkIdentifier + 9vvzujtq5s.skadnetwork + + + SKAdNetworkIdentifier + 9yg77x724h.skadnetwork + + + SKAdNetworkIdentifier + a2p9lx4jpn.skadnetwork + + + SKAdNetworkIdentifier + a7xqa6mtl2.skadnetwork + + + SKAdNetworkIdentifier + a8cz6cu7e5.skadnetwork + + + SKAdNetworkIdentifier + av6w8kgt66.skadnetwork + + + SKAdNetworkIdentifier + b9bk5wbcq9.skadnetwork + + + SKAdNetworkIdentifier + bvpn9ufa9b.skadnetwork + + + SKAdNetworkIdentifier + bxvub5ada5.skadnetwork + + + SKAdNetworkIdentifier + c3frkrj4fj.skadnetwork + + + SKAdNetworkIdentifier + c6k4g5qg8m.skadnetwork + + + SKAdNetworkIdentifier + ce8ybjwass.skadnetwork + + + SKAdNetworkIdentifier + cg4yq2srnc.skadnetwork + + + SKAdNetworkIdentifier + cj5566h2ga.skadnetwork + + + SKAdNetworkIdentifier + cp8zw746q7.skadnetwork + + + SKAdNetworkIdentifier + cs644xg564.skadnetwork + + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + + SKAdNetworkIdentifier + cwn433xbcr.skadnetwork + + + SKAdNetworkIdentifier + dbu4b84rxf.skadnetwork + + + SKAdNetworkIdentifier + dkc879ngq3.skadnetwork + + + SKAdNetworkIdentifier + dt3cjx1a9i.skadnetwork + + + SKAdNetworkIdentifier + dzg6xy7pwj.skadnetwork + + + SKAdNetworkIdentifier + e5fvkxwrpn.skadnetwork + + + SKAdNetworkIdentifier + ecpz2srf59.skadnetwork + + + SKAdNetworkIdentifier + eh6m2bh4zr.skadnetwork + + + SKAdNetworkIdentifier + ejvt5qm6ak.skadnetwork + + + SKAdNetworkIdentifier + f38h382jlk.skadnetwork + + + SKAdNetworkIdentifier + f73kdq92p3.skadnetwork + + + SKAdNetworkIdentifier + f7s53z58qe.skadnetwork + + + SKAdNetworkIdentifier + feyaarzu9v.skadnetwork + + + SKAdNetworkIdentifier + fq6vru337s.skadnetwork + + + SKAdNetworkIdentifier + fz2k2k5tej.skadnetwork + + + SKAdNetworkIdentifier + g28c52eehv.skadnetwork + + + SKAdNetworkIdentifier + g2y4y55b64.skadnetwork + + + SKAdNetworkIdentifier + g69uk9uh2b.skadnetwork + + + SKAdNetworkIdentifier + g6gcrrvk4p.skadnetwork + + + SKAdNetworkIdentifier + ggvn48r87g.skadnetwork + + + SKAdNetworkIdentifier + glqzh8vgby.skadnetwork + + + SKAdNetworkIdentifier + gta8lk7p23.skadnetwork + + + SKAdNetworkIdentifier + gta9lk7p23.skadnetwork + + + SKAdNetworkIdentifier + gvmwg8q7h5.skadnetwork + + + SKAdNetworkIdentifier + h65wbv5k3f.skadnetwork + + + SKAdNetworkIdentifier + hb56zgv37p.skadnetwork + + + SKAdNetworkIdentifier + hdw39hrw9y.skadnetwork + + + SKAdNetworkIdentifier + hjevpa356n.skadnetwork + + + SKAdNetworkIdentifier + hs6bdukanm.skadnetwork + + + SKAdNetworkIdentifier + jk2fsx2rgz.skadnetwork + + + SKAdNetworkIdentifier + k674qkevps.skadnetwork + + + SKAdNetworkIdentifier + k6y4y55b64.skadnetwork + + + SKAdNetworkIdentifier + kbd757ywx3.skadnetwork + + + SKAdNetworkIdentifier + kbmxgpxpgc.skadnetwork + + + SKAdNetworkIdentifier + klf5c3l5u5.skadnetwork + + + SKAdNetworkIdentifier + krvm3zuq6h.skadnetwork + + + SKAdNetworkIdentifier + ln5gz23vtd.skadnetwork + + + SKAdNetworkIdentifier + lr83yxwka7.skadnetwork + + + SKAdNetworkIdentifier + ludvb6z3bs.skadnetwork + + + SKAdNetworkIdentifier + m297p6643m.skadnetwork + + + SKAdNetworkIdentifier + m5mvw97r93.skadnetwork + + + SKAdNetworkIdentifier + m8dbw4sv7c.skadnetwork + + + SKAdNetworkIdentifier + mj797d8u6f.skadnetwork + + + SKAdNetworkIdentifier + mlmmfzh3r3.skadnetwork + + + SKAdNetworkIdentifier + mls7yz5dvl.skadnetwork + + + SKAdNetworkIdentifier + mp6xlyr22a.skadnetwork + + + SKAdNetworkIdentifier + mqn7fxpca7.skadnetwork + + + SKAdNetworkIdentifier + mtkv5xtk9e.skadnetwork + + + SKAdNetworkIdentifier + n38lu8286q.skadnetwork + + + SKAdNetworkIdentifier + n66cz3y3bx.skadnetwork + + + SKAdNetworkIdentifier + n6fk4nfna4.skadnetwork + + + SKAdNetworkIdentifier + n9x2a789qt.skadnetwork + + + SKAdNetworkIdentifier + ns5j362hk7.skadnetwork + + + SKAdNetworkIdentifier + nu4557a4je.skadnetwork + + + SKAdNetworkIdentifier + nzq8sh4pbs.skadnetwork + + + SKAdNetworkIdentifier + p78axxw29g.skadnetwork + + + SKAdNetworkIdentifier + ppxm28t8ap.skadnetwork + + + SKAdNetworkIdentifier + prcb7njmu6.skadnetwork + + + SKAdNetworkIdentifier + pu4na253f3.skadnetwork + + + SKAdNetworkIdentifier + pwa73g5rt2.skadnetwork + + + SKAdNetworkIdentifier + pwdxu55a5a.skadnetwork + + + SKAdNetworkIdentifier + qqp299437r.skadnetwork + + + SKAdNetworkIdentifier + qu637u8glc.skadnetwork + + + SKAdNetworkIdentifier + qwpu75vrh2.skadnetwork + + + SKAdNetworkIdentifier + r45fhb6rf7.skadnetwork + + + SKAdNetworkIdentifier + r8lj5b58b5.skadnetwork + + + SKAdNetworkIdentifier + rvh3l7un93.skadnetwork + + + SKAdNetworkIdentifier + rx5hdcabgc.skadnetwork + + + SKAdNetworkIdentifier + s39g8k73mm.skadnetwork + + + SKAdNetworkIdentifier + s69wq72ugq.skadnetwork + + + SKAdNetworkIdentifier + su67r6k2v3.skadnetwork + + + SKAdNetworkIdentifier + t38b2kh725.skadnetwork + + + SKAdNetworkIdentifier + t6d3zquu66.skadnetwork + + + SKAdNetworkIdentifier + tl55sbb4fm.skadnetwork + + + SKAdNetworkIdentifier + tmhh9296z4.skadnetwork + + + SKAdNetworkIdentifier + tvvz7th9br.skadnetwork + + + SKAdNetworkIdentifier + u679fj5vs4.skadnetwork + + + SKAdNetworkIdentifier + uw77j35x4d.skadnetwork + + + SKAdNetworkIdentifier + v4nxqhlyqp.skadnetwork + + + SKAdNetworkIdentifier + v72qych5uu.skadnetwork + + + SKAdNetworkIdentifier + v79kvwwj4g.skadnetwork + + + SKAdNetworkIdentifier + v9wttpbfk9.skadnetwork + + + SKAdNetworkIdentifier + vcra2ehyfk.skadnetwork + + + SKAdNetworkIdentifier + vhf287vqwu.skadnetwork + + + SKAdNetworkIdentifier + vutu7akeur.skadnetwork + + + SKAdNetworkIdentifier + w7jznl3r6g.skadnetwork + + + SKAdNetworkIdentifier + w9q455wk68.skadnetwork + + + SKAdNetworkIdentifier + wg4vff78zm.skadnetwork + + + SKAdNetworkIdentifier + wzmmz9fp6w.skadnetwork + + + SKAdNetworkIdentifier + x44k69ngh6.skadnetwork + + + SKAdNetworkIdentifier + x5l83yy675.skadnetwork + + + SKAdNetworkIdentifier + x8jxxk4ff5.skadnetwork + + + SKAdNetworkIdentifier + x8uqf25wch.skadnetwork + + + SKAdNetworkIdentifier + xga6mpmplv.skadnetwork + + + SKAdNetworkIdentifier + xy9t38ct57.skadnetwork + + + SKAdNetworkIdentifier + y45688jllp.skadnetwork + + + SKAdNetworkIdentifier + y5ghdn5j9k.skadnetwork + + + SKAdNetworkIdentifier + yclnxrl5pm.skadnetwork + + + SKAdNetworkIdentifier + ydx93a7ass.skadnetwork + + + SKAdNetworkIdentifier + yrqqpx2mcb.skadnetwork + + + SKAdNetworkIdentifier + z24wtl6j62.skadnetwork + + + SKAdNetworkIdentifier + z4gj7hsk7h.skadnetwork + + + SKAdNetworkIdentifier + z959bm4gru.skadnetwork + + + SKAdNetworkIdentifier + zmvfpc5aq8.skadnetwork + + + SKAdNetworkIdentifier + zq492l623r.skadnetwork + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + AtmoSphere + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + atmo_sphere + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.1 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSLocationAlwaysUsageDescription + AtmoSphere needs access to your location to show weather information for your current position. + NSLocationWhenInUseUsageDescription + AtmoSphere needs access to your location to show weather information for your current position. + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/stonicons.png b/ios/Runner/stonicons.png new file mode 100644 index 0000000..ede3686 Binary files /dev/null and b/ios/Runner/stonicons.png differ diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..6247640 --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,32 @@ +import 'package:atmo_sphere/tools/start_page.dart'; +import 'package:flutter/material.dart'; +import 'constants.dart'; +import 'screens/main_screen.dart'; + +class AtmoSphereApp extends StatelessWidget { + final NavigatorObserver analyticsObserver; + const AtmoSphereApp({super.key,required this.analyticsObserver}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'AtmoSphere', + navigatorObservers: [analyticsObserver], + theme: ThemeData( + brightness: Brightness.dark, + primarySwatch: Colors.blue, + fontFamily: 'Inter', + scaffoldBackgroundColor: kDarkPurple, + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + elevation: 0, + centerTitle: true, + ), + ), + debugShowCheckedModeBanner: false, + home: StartPage( + homePageBuilder: () => const MainScreen(), + ), + ); + } +} diff --git a/lib/constants.dart b/lib/constants.dart new file mode 100644 index 0000000..762d0e8 --- /dev/null +++ b/lib/constants.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +const String weatherApiKey = 'f4ae82990f834955a0385536251808'; +const String weatherApiBaseUrl = 'https://api.weatherapi.com/v1'; + +// 默认查询城市 +const String defaultCity = 'sanya'; + +// 样式常量 +const Color kDarkPurple = Color(0xFF1C1C2E); +final Color kCardBackground = Colors.black.withOpacity(0.25); +final BorderRadius kCardBorderRadius = BorderRadius.circular(20.0); diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart new file mode 100644 index 0000000..b9317d4 --- /dev/null +++ b/lib/generated/assets.dart @@ -0,0 +1,22 @@ +///This file is automatically generated. DO NOT EDIT, all your changes would be lost. +class Assets { + Assets._(); + + static const String clearDayImage1 = 'assets/wallpapers/clear_day/image1.png'; + static const String clearDayImage2 = 'assets/wallpapers/clear_day/image2.png'; + static const String clearDayImage3 = 'assets/wallpapers/clear_day/image3.png'; + static const String clearNightImage1 = 'assets/wallpapers/clear_night/image1.png'; + static const String clearNightImage2 = 'assets/wallpapers/clear_night/image2.png'; + static const String clearNightImage3 = 'assets/wallpapers/clear_night/image3.png'; + static const String clearNightImage4 = 'assets/wallpapers/clear_night/image4.png'; + static const String clearNightImage5 = 'assets/wallpapers/clear_night/image5.png'; + static const String cloudyNightImage1 = 'assets/wallpapers/cloudy_night/image1.png'; + static const String defaultImage1 = 'assets/wallpapers/default/image1.png'; + static const String fogDayImage1 = 'assets/wallpapers/fog_day/image1.png'; + static const String fogNightImage1 = 'assets/wallpapers/fog_night/image1.png'; + static const String rainDayImage1 = 'assets/wallpapers/rain_day/image1.png'; + static const String rainNightImage1 = 'assets/wallpapers/rain_night/image1.png'; + static const String snowDayImage1 = 'assets/wallpapers/snow_day/image1.png'; + static const String snowNightImage1 = 'assets/wallpapers/snow_night/image1.png'; + +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..9cc2a46 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,104 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:atmo_sphere/tools/firebase_options.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_analytics/observer.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'app.dart'; + +bool isFirebaseInitialized = false; +void main() async { + + runZonedGuarded>(()async{ + + WidgetsFlutterBinding.ensureInitialized(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]); + + try { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ).timeout(const Duration(seconds: 15)); // 设置15秒超时 + print("✅ Firebase 初始化成功。"); + isFirebaseInitialized = true; // 成功后设置标志 + + } on TimeoutException { + } catch (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack, fatal: false); + } + + // Firebase 成功初始化后,配置依赖它的服务 + if (isFirebaseInitialized) { + //设置全局错误处理器 + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + return true; + }; + // 使用我们的安全服务来记录 App Open + AnalyticsService.instance.logAppOpen(); + } + + + final analyticsObserver = SafeFirebaseAnalyticsObserver(); + runApp( ProviderScope(child: AtmoSphereApp(analyticsObserver: analyticsObserver))); + + }, (e,s){}); + + +} + +class AnalyticsService { + // 单例模式 + AnalyticsService._(); + static final instance = AnalyticsService._(); + + final _analytics = FirebaseAnalytics.instance; + + void logAppOpen() { + if (isFirebaseInitialized) { + _analytics.logAppOpen(); + } else { + print("[AnalyticsService] Skipped logAppOpen: Firebase not initialized."); + } + } + + +} + +class SafeFirebaseAnalyticsObserver extends NavigatorObserver { + late final FirebaseAnalyticsObserver _observer; + + SafeFirebaseAnalyticsObserver() { + if (isFirebaseInitialized) { + _observer = FirebaseAnalyticsObserver(analytics: AnalyticsService.instance._analytics); + } + } + + @override + void didPush(Route route, Route? previousRoute) { + if (isFirebaseInitialized) { + _observer.didPush(route, previousRoute); + } + } + + @override + void didPop(Route route, Route? previousRoute) { + if (isFirebaseInitialized) { + _observer.didPop(route, previousRoute); + } + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + if (isFirebaseInitialized) { + _observer.didReplace(newRoute: newRoute, oldRoute: oldRoute); + } + } +} \ No newline at end of file diff --git a/lib/models/city_model.dart b/lib/models/city_model.dart new file mode 100644 index 0000000..11a4949 --- /dev/null +++ b/lib/models/city_model.dart @@ -0,0 +1,72 @@ +class CityModel { + final String name; + final String? region; + final String? country; + final String query; // 用于API查询的字符串 + final DateTime addedAt; + final bool isDefault; // 是否为默认城市 + + CityModel({ + required this.name, + this.region, + this.country, + required this.query, + DateTime? addedAt, + this.isDefault = false, + }) : addedAt = addedAt ?? DateTime.now(); + + // 转换为JSON用于存储 + Map toJson() { + return { + 'name': name, + 'region': region, + 'country': country, + 'query': query, + 'addedAt': addedAt.toIso8601String(), + 'isDefault': isDefault, + }; + } + + // 从JSON创建 + factory CityModel.fromJson(Map json) { + return CityModel( + name: json['name'], + region: json['region'], + country: json['country'], + query: json['query'], + addedAt: DateTime.parse(json['addedAt']), + isDefault: json['isDefault'] ?? false, + ); + } + + // 复制方法 + CityModel copyWith({ + String? name, + String? region, + String? country, + String? query, + DateTime? addedAt, + bool? isDefault, + }) { + return CityModel( + name: name ?? this.name, + region: region ?? this.region, + country: country ?? this.country, + query: query ?? this.query, + addedAt: addedAt ?? this.addedAt, + isDefault: isDefault ?? this.isDefault, + ); + } + + // 显示名称 + String get displayName { + if (region != null && country != null) { + return '$name, $region, $country'; + } else if (region != null) { + return '$name, $region'; + } else if (country != null) { + return '$name, $country'; + } + return name; + } +} diff --git a/lib/models/weather_models.dart b/lib/models/weather_models.dart new file mode 100644 index 0000000..98ace9d --- /dev/null +++ b/lib/models/weather_models.dart @@ -0,0 +1,190 @@ +class WeatherModel { + final Location location; + final CurrentWeather current; + final Forecast forecast; + + WeatherModel({ + required this.location, + required this.current, + required this.forecast, + }); + + factory WeatherModel.fromJson(Map json) { + return WeatherModel( + location: Location.fromJson(json['location']), + current: CurrentWeather.fromJson(json['current']), + forecast: Forecast.fromJson(json['forecast']), + ); + } +} + +class Location { + final String name; + final String region; + final String country; + final String localtime; + + Location({ + required this.name, + required this.region, + required this.country, + required this.localtime, + }); + + factory Location.fromJson(Map json) { + return Location( + name: json['name'], + region: json['region'], + country: json['country'], + localtime: json['localtime'], + ); + } +} + +class CurrentWeather { + final double tempC; + final double feelslikeC; + final Condition condition; + final double windKph; + final int humidity; + final double uv; + final int isDay; // 1 = Yes, 0 = No + + CurrentWeather({ + required this.tempC, + required this.feelslikeC, + required this.condition, + required this.windKph, + required this.humidity, + required this.uv, + required this.isDay, + }); + + factory CurrentWeather.fromJson(Map json) { + return CurrentWeather( + tempC: json['temp_c'].toDouble(), + feelslikeC: json['feelslike_c'].toDouble(), + condition: Condition.fromJson(json['condition']), + windKph: json['wind_kph'].toDouble(), + humidity: json['humidity'], + uv: json['uv'].toDouble(), + isDay: json['is_day'], + ); + } +} + +class Condition { + final String text; + final String icon; + final int code; // 天气状况码,用于匹配壁纸 + + Condition({required this.text, required this.icon, required this.code}); + + factory Condition.fromJson(Map json) { + return Condition( + text: json['text'], + icon: "https:${json['icon']}", + code: json['code'], + ); + } +} + +class Forecast { + final List forecastday; + + Forecast({required this.forecastday}); + + factory Forecast.fromJson(Map json) { + var list = json['forecastday'] as List; + List forecastDayList = list + .map((i) => ForecastDay.fromJson(i)) + .toList(); + return Forecast(forecastday: forecastDayList); + } +} + +class ForecastDay { + final String date; + final Day day; + final Astro astro; + final List hour; + + ForecastDay({ + required this.date, + required this.day, + required this.astro, + required this.hour, + }); + + factory ForecastDay.fromJson(Map json) { + var hourList = json['hour'] as List; + List hours = hourList.map((i) => Hour.fromJson(i)).toList(); + + return ForecastDay( + date: json['date'], + day: Day.fromJson(json['day']), + astro: Astro.fromJson(json['astro']), + hour: hours, + ); + } +} + +class Day { + final double maxtempC; + final double mintempC; + final double avgtempC; + final Condition condition; + final int dailyChanceOfRain; + + Day({ + required this.maxtempC, + required this.mintempC, + required this.avgtempC, + required this.condition, + required this.dailyChanceOfRain, + }); + + factory Day.fromJson(Map json) { + return Day( + maxtempC: json['maxtemp_c'].toDouble(), + mintempC: json['mintemp_c'].toDouble(), + avgtempC: json['avgtemp_c'].toDouble(), + condition: Condition.fromJson(json['condition']), + dailyChanceOfRain: json['daily_chance_of_rain'], + ); + } +} + +class Astro { + final String sunrise; + final String sunset; + + Astro({required this.sunrise, required this.sunset}); + + factory Astro.fromJson(Map json) { + return Astro(sunrise: json['sunrise'], sunset: json['sunset']); + } +} + +class Hour { + final String time; + final double tempC; + final Condition condition; + final int chanceOfRain; + + Hour({ + required this.time, + required this.tempC, + required this.condition, + required this.chanceOfRain, + }); + + factory Hour.fromJson(Map json) { + return Hour( + time: json['time'], + tempC: json['temp_c'].toDouble(), + condition: Condition.fromJson(json['condition']), + chanceOfRain: json['chance_of_rain'], + ); + } +} diff --git a/lib/providers.dart b/lib/providers.dart new file mode 100644 index 0000000..9d55ecb --- /dev/null +++ b/lib/providers.dart @@ -0,0 +1,80 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'models/weather_models.dart'; +import 'repositories/weather_repository.dart'; +import 'services/wallpaper_service.dart'; +import 'providers/city_provider.dart'; + +// 仓库 Provider +final weatherRepositoryProvider = Provider((ref) { + return WeatherRepository(client: http.Client()); +}); + +// 天气状态 Notifier +final weatherProvider = AsyncNotifierProvider( + () { + return WeatherNotifier(); + }, +); + +class WeatherNotifier extends AsyncNotifier { + @override + Future build() async { + // 从城市管理获取当前城市 + final cityListNotifier = ref.read(cityListProvider.notifier); + final currentCity = await cityListNotifier.getCurrentCity(); + + // 监听当前城市变化 + ref.listen(currentCityProvider, (previous, next) { + next.whenData((city) { + if (previous?.value != city) { + refetchWeather(city); + } + }); + }); + + return _fetchWeather(currentCity); + } + + Future _fetchWeather(String city) async { + final repository = ref.read(weatherRepositoryProvider); + return await repository.fetchWeather(city); + } + + Future refetchWeather(String city) async { + state = const AsyncValue.loading(); + try { + final weather = await _fetchWeather(city); + state = AsyncValue.data(weather); + } catch (e, s) { + state = AsyncValue.error(e, s); + } + } + + // 切换城市并刷新天气 + Future switchCity(String city) async { + final cityListNotifier = ref.read(cityListProvider.notifier); + await cityListNotifier.setCurrentCity(city); + await refetchWeather(city); + } +} + +// 壁纸服务 Provider +final wallpaperServiceProvider = Provider((ref) { + return WallpaperService(); +}); + +// 动态壁纸路径 Provider +final dynamicWallpaperProvider = Provider((ref) { + final weatherAsync = ref.watch(weatherProvider); + final wallpaperService = ref.watch(wallpaperServiceProvider); + return weatherAsync.when( + data: (weather) { + final code = weather.current.condition.code; + final isDay = weather.current.isDay == 1; + return wallpaperService.getWallpaper(code, isDay); + }, + loading: () => wallpaperService.getWallpaper(1000, true), + error: (e, s) => wallpaperService.getWallpaper(1000, true), + ); +}); diff --git a/lib/providers/city_provider.dart b/lib/providers/city_provider.dart new file mode 100644 index 0000000..1b508f2 --- /dev/null +++ b/lib/providers/city_provider.dart @@ -0,0 +1,180 @@ +import 'dart:convert'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../models/city_model.dart'; +import '../constants.dart'; + +// SharedPreferences Provider +final sharedPreferencesProvider = Provider>(( + ref, +) async { + return SharedPreferences.getInstance(); +}); + +// 城市列表 Provider +final cityListProvider = NotifierProvider>( + () { + final notifier = CityListNotifier(); + notifier._initialize(); + return notifier; + }, +); + +class CityListNotifier extends Notifier> { + static const String _citiesKey = 'saved_cities'; + static const String _currentCityKey = 'current_city'; + bool _initialized = false; + + @override + List build() { + if (!_initialized) { + _initialize(); + } + return []; + } + + // 初始化加载城市列表 + Future _initialize() async { + if (_initialized) return; + _initialized = true; + await _loadCities(); + } + + // 加载保存的城市列表 + Future _loadCities() async { + final prefs = await SharedPreferences.getInstance(); + final citiesJson = prefs.getStringList(_citiesKey); + + if (citiesJson != null && citiesJson.isNotEmpty) { + final cities = citiesJson + .map((json) => CityModel.fromJson(jsonDecode(json))) + .toList(); + state = cities; + } else { + // 如果没有保存的城市,添加默认城市 + final defaultCityModel = CityModel( + name: defaultCity.split(',').first.trim(), + query: defaultCity, + isDefault: true, + ); + state = [defaultCityModel]; + await _saveCities(); + } + } + + // 保存城市列表 + Future _saveCities() async { + final prefs = await SharedPreferences.getInstance(); + final citiesJson = state.map((city) => jsonEncode(city.toJson())).toList(); + await prefs.setStringList(_citiesKey, citiesJson); + } + + // 添加城市 + Future addCity(CityModel city) async { + // 检查是否已存在 + if (state.any((c) => c.query.toLowerCase() == city.query.toLowerCase())) { + return false; // 城市已存在 + } + + // 如果这是第一个城市,设为默认 + final newCity = state.isEmpty + ? city.copyWith(isDefault: true) + : city.copyWith(isDefault: false); + + state = [...state, newCity]; + await _saveCities(); + return true; + } + + // 获取当前状态 + List get currentState => state; + + // 删除城市 + Future removeCity(String query) async { + if (state.length <= 1) { + return; // 至少保留一个城市 + } + + final cityToRemove = state.firstWhere((c) => c.query == query); + final wasDefault = cityToRemove.isDefault; + + state = state.where((c) => c.query != query).toList(); + + // 如果删除的是默认城市,将第一个城市设为默认 + if (wasDefault && state.isNotEmpty) { + state = [state.first.copyWith(isDefault: true), ...state.skip(1)]; + await setCurrentCity(state.first.query); + } + + await _saveCities(); + } + + // 设置当前城市 + Future setCurrentCity(String query) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_currentCityKey, query); + + // 更新默认标记 + state = state.map((city) { + return city.copyWith(isDefault: city.query == query); + }).toList(); + await _saveCities(); + } + + // 获取当前城市 + Future getCurrentCity() async { + // 确保已初始化 + if (!_initialized) { + await _initialize(); + } + + // 如果 state 为空,等待加载完成 + if (state.isEmpty) { + await _loadCities(); + } + + final prefs = await SharedPreferences.getInstance(); + final currentCityQuery = prefs.getString(_currentCityKey); + + if (currentCityQuery != null && + state.any((c) => c.query == currentCityQuery)) { + return currentCityQuery; + } + + // 如果没有当前城市或当前城市不在列表中,返回默认城市 + if (state.isEmpty) { + // 如果仍然为空,返回默认城市查询字符串 + return defaultCity; + } + + final defaultCityModel = state.firstWhere( + (c) => c.isDefault, + orElse: () => state.first, + ); + return defaultCityModel.query; + } + + // 通过查询字符串查找城市 + CityModel? findCityByQuery(String query) { + try { + return state.firstWhere( + (c) => c.query.toLowerCase() == query.toLowerCase(), + ); + } catch (e) { + return null; + } + } +} + +// 当前城市 Provider +final currentCityProvider = FutureProvider((ref) async { + final cityListNotifier = ref.read(cityListProvider.notifier); + return await cityListNotifier.getCurrentCity(); +}); + +// 城市列表 Notifier 扩展方法 +extension CityListNotifierExtension on CityListNotifier { + Future addCityAsync(CityModel city) => addCity(city); + Future removeCityAsync(String query) => removeCity(query); + Future setCurrentCityAsync(String query) => setCurrentCity(query); +} diff --git a/lib/providers/main_screen_provider.dart b/lib/providers/main_screen_provider.dart new file mode 100644 index 0000000..3551998 --- /dev/null +++ b/lib/providers/main_screen_provider.dart @@ -0,0 +1,29 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// MainScreen 当前选中的 tab 索引 Notifier +class MainScreenIndexNotifier extends Notifier { + @override + int build() { + return 0; + } + + void setIndex(int index) { + state = index; + } + + void switchToHome() { + state = 0; + } +} + +// MainScreen 当前选中的 tab 索引 Provider +final mainScreenIndexProvider = NotifierProvider( + () { + return MainScreenIndexNotifier(); + }, +); + +// 切换到主页的方法 +void switchToHome(WidgetRef ref) { + ref.read(mainScreenIndexProvider.notifier).switchToHome(); +} diff --git a/lib/repositories/weather_repository.dart b/lib/repositories/weather_repository.dart new file mode 100644 index 0000000..2026ed3 --- /dev/null +++ b/lib/repositories/weather_repository.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import '../constants.dart'; +import '../models/weather_models.dart'; + +class WeatherRepository { + final http.Client client; + WeatherRepository({required this.client}); + + Future fetchWeather(String city) async { + final url = + '$weatherApiBaseUrl/forecast.json?key=$weatherApiKey&q=$city&days=7&aqi=no&alerts=no'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final data = json.decode(response.body); + return WeatherModel.fromJson(data); + } else { + throw Exception( + 'Failed to load weather data (Code: ${response.statusCode})', + ); + } + } catch (e) { + throw Exception('Failed to fetch weather: $e'); + } + } +} diff --git a/lib/screens/about_screen.dart b/lib/screens/about_screen.dart new file mode 100644 index 0000000..e4c8800 --- /dev/null +++ b/lib/screens/about_screen.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; + +class AboutScreen extends StatelessWidget { + const AboutScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('About AtmoSphere')), + body: ListView( + padding: const EdgeInsets.all(24.0), + children: [ + const SizedBox(height: 20), + // App Icon/Logo + Center( + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Colors.blueAccent.withOpacity(0.2), + borderRadius: BorderRadius.circular(30), + ), + child: const Icon( + Icons.wb_sunny, + size: 60, + color: Colors.blueAccent, + ), + ), + ), + const SizedBox(height: 32), + // App Name + const Center( + child: Text( + 'AtmoSphere', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 8), + // Version + Center( + child: Text( + 'Version 1.1.0', + style: TextStyle( + fontSize: 16, + color: Colors.white.withOpacity(0.7), + ), + ), + ), + const SizedBox(height: 48), + // Description + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Description', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 12), + Text( + 'AtmoSphere is a beautiful weather app that provides real-time weather information with dynamic wallpapers that change based on current weather conditions. Stay informed about the weather in your favorite cities with an elegant and intuitive interface.', + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.8), + height: 1.5, + ), + ), + ], + ), + ), + const SizedBox(height: 24), + // Features + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Features', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 16), + _FeatureItem( + icon: Icons.location_on, + text: 'Multi-city weather tracking', + ), + const SizedBox(height: 12), + _FeatureItem( + icon: Icons.wallpaper, + text: 'Dynamic wallpapers based on weather', + ), + const SizedBox(height: 12), + _FeatureItem( + icon: Icons.calendar_today, + text: '7-day weather forecast', + ), + const SizedBox(height: 12), + _FeatureItem( + icon: Icons.notifications, + text: 'Weather alerts and notifications', + ), + ], + ), + ), + const SizedBox(height: 24), + // Copyright + Center( + child: Text( + '© 2024 AtmoSphere', + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.5), + ), + ), + ), + const SizedBox(height: 8), + Center( + child: Text( + 'All rights reserved', + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.5), + ), + ), + ), + const SizedBox(height: 40), + ], + ), + ); + } +} + +class _FeatureItem extends StatelessWidget { + final IconData icon; + final String text; + + const _FeatureItem({required this.icon, required this.text}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon(icon, color: Colors.blueAccent, size: 20), + const SizedBox(width: 12), + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.8), + ), + ), + ), + ], + ); + } +} diff --git a/lib/screens/city_management_screen.dart b/lib/screens/city_management_screen.dart new file mode 100644 index 0000000..b3bbee1 --- /dev/null +++ b/lib/screens/city_management_screen.dart @@ -0,0 +1,327 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../constants.dart'; +import '../models/city_model.dart'; +import '../providers/city_provider.dart'; +import '../providers.dart'; + +class CityManagementScreen extends ConsumerStatefulWidget { + const CityManagementScreen({super.key}); + + @override + ConsumerState createState() => + _CityManagementScreenState(); +} + +class _CityManagementScreenState extends ConsumerState { + final TextEditingController _searchController = TextEditingController(); + bool _isSearching = false; + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + Future _addCity(String cityQuery) async { + if (cityQuery.trim().isEmpty) { + _showSnackBar('Please enter a city name'); + return; + } + + try { + // 先尝试获取天气数据以验证城市是否存在 + final repository = ref.read(weatherRepositoryProvider); + final weather = await repository.fetchWeather(cityQuery.trim()); + + // 创建城市模型 + final city = CityModel( + name: weather.location.name, + region: weather.location.region, + country: weather.location.country, + query: cityQuery.trim(), + ); + + // 添加到列表 + final success = await ref.read(cityListProvider.notifier).addCity(city); + + if (success) { + _showSnackBar('City added successfully'); + _searchController.clear(); + setState(() { + _isSearching = false; + }); + } else { + _showSnackBar('City already exists'); + } + } catch (e) { + _showSnackBar('Failed to add city: ${e.toString()}'); + } + } + + Future _removeCity(String query) async { + final cities = ref.read(cityListProvider); + if (cities.length <= 1) { + _showSnackBar('At least one city must remain'); + return; + } + + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Delete City'), + content: const Text('Are you sure you want to remove this city?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('Delete', style: TextStyle(color: Colors.red)), + ), + ], + ), + ); + + if (confirmed == true) { + await ref.read(cityListProvider.notifier).removeCity(query); + _showSnackBar('City removed'); + } + } + + Future _switchCity(String query) async { + await ref.read(weatherProvider.notifier).switchCity(query); + if (mounted) { + Navigator.pop(context); + _showSnackBar('City switched'); + } + } + + void _showSnackBar(String message) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), duration: const Duration(seconds: 2)), + ); + } + } + + @override + Widget build(BuildContext context) { + final cities = ref.watch(cityListProvider); + final currentCityAsync = ref.watch(currentCityProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Manage Cities'), + actions: [ + IconButton( + icon: Icon(_isSearching ? Icons.close : Icons.add), + onPressed: () { + setState(() { + _isSearching = !_isSearching; + if (!_isSearching) { + _searchController.clear(); + } + }); + }, + ), + ], + ), + body: Column( + children: [ + if (_isSearching) + Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: kCardBackground, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _searchController, + decoration: InputDecoration( + hintText: 'Enter city name (e.g., London, Beijing)', + hintStyle: TextStyle( + color: Colors.white.withOpacity(0.7), + ), + filled: true, + fillColor: Colors.black.withOpacity(0.3), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + style: const TextStyle(color: Colors.white), + onSubmitted: _addCity, + ), + ), + const SizedBox(width: 8), + IconButton( + icon: const Icon(Icons.search), + onPressed: () => _addCity(_searchController.text), + ), + ], + ), + ), + Expanded( + child: cities.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.location_city, + size: 64, + color: Colors.white.withOpacity(0.5), + ), + const SizedBox(height: 16), + Text( + 'No cities added', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 18, + ), + ), + const SizedBox(height: 8), + Text( + 'Tap + to add a city', + style: TextStyle( + color: Colors.white.withOpacity(0.5), + fontSize: 14, + ), + ), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(16.0), + itemCount: cities.length, + itemBuilder: (context, index) { + final city = cities[index]; + return currentCityAsync.when( + data: (currentQuery) => _CityCard( + city: city, + isCurrent: city.query == currentQuery, + onTap: () => _switchCity(city.query), + onDelete: cities.length > 1 + ? () => _removeCity(city.query) + : null, + ), + loading: () => _CityCard( + city: city, + isCurrent: city.isDefault, + onTap: () => _switchCity(city.query), + onDelete: cities.length > 1 + ? () => _removeCity(city.query) + : null, + ), + error: (_, __) => _CityCard( + city: city, + isCurrent: city.isDefault, + onTap: () => _switchCity(city.query), + onDelete: cities.length > 1 + ? () => _removeCity(city.query) + : null, + ), + ); + }, + ), + ), + ], + ), + ); + } +} + +class _CityCard extends StatelessWidget { + final CityModel city; + final bool isCurrent; + final VoidCallback onTap; + final VoidCallback? onDelete; + + const _CityCard({ + required this.city, + required this.isCurrent, + required this.onTap, + this.onDelete, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12.0), + decoration: BoxDecoration( + color: isCurrent ? Colors.blue.withOpacity(0.3) : kCardBackground, + borderRadius: kCardBorderRadius, + border: isCurrent + ? Border.all(color: Colors.blueAccent, width: 2) + : null, + ), + child: ListTile( + leading: CircleAvatar( + backgroundColor: isCurrent + ? Colors.blueAccent + : Colors.white.withOpacity(0.2), + child: Icon( + isCurrent ? Icons.location_on : Icons.location_city, + color: Colors.white, + ), + ), + title: Text( + city.name, + style: TextStyle( + fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal, + color: Colors.white, + ), + ), + subtitle: Text( + city.displayName, + style: TextStyle(color: Colors.white.withOpacity(0.7), fontSize: 12), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isCurrent) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.blueAccent, + borderRadius: BorderRadius.circular(12), + ), + child: const Text( + 'Current', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + if (onDelete != null) ...[ + const SizedBox(width: 8), + IconButton( + icon: const Icon(Icons.delete_outline, color: Colors.red), + onPressed: onDelete, + tooltip: 'Delete city', + ), + ], + ], + ), + onTap: onTap, + ), + ); + } +} diff --git a/lib/screens/feedback_screen.dart b/lib/screens/feedback_screen.dart new file mode 100644 index 0000000..fb86a31 --- /dev/null +++ b/lib/screens/feedback_screen.dart @@ -0,0 +1,328 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; + +class FeedbackScreen extends StatefulWidget { + const FeedbackScreen({super.key}); + + @override + State createState() => _FeedbackScreenState(); +} + +class _FeedbackScreenState extends State { + final TextEditingController _feedbackController = TextEditingController(); + final _formKey = GlobalKey(); + bool _isSubmitting = false; + String? _selectedFeedbackType; // 选中的反馈类型 + + @override + void dispose() { + _feedbackController.dispose(); + super.dispose(); + } + + Future _submitFeedback() async { + if (!_formKey.currentState!.validate()) { + return; + } + + setState(() { + _isSubmitting = true; + }); + + // 模拟提交反馈(实际应用中这里应该调用API) + await Future.delayed(const Duration(milliseconds: 500)); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Thank you for your feedback!'), + duration: Duration(seconds: 1), + ), + ); + + // 0.8秒后返回上一页 + await Future.delayed(const Duration(milliseconds: 800)); + + if (mounted) { + Navigator.pop(context); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Feedback')), + body: Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.all(24.0), + children: [ + const SizedBox(height: 20), + // Header + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.feedback, + size: 48, + color: Colors.blueAccent, + ), + const SizedBox(height: 16), + const Text( + 'We\'d love to hear from you!', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + Text( + 'Your feedback helps us improve AtmoSphere. Please share your thoughts, suggestions, or report any issues.', + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.7), + height: 1.5, + ), + ), + ], + ), + ), + const SizedBox(height: 32), + // Feedback Type Selection + Text( + 'Feedback Type', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white.withOpacity(0.9), + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: Column( + children: [ + _FeedbackTypeOption( + icon: Icons.bug_report, + title: 'Bug Report', + subtitle: 'Report a problem or issue', + isSelected: _selectedFeedbackType == 'bug', + onTap: () { + setState(() { + _selectedFeedbackType = 'bug'; + }); + }, + ), + const Divider(color: Colors.white24), + _FeedbackTypeOption( + icon: Icons.lightbulb_outline, + title: 'Feature Request', + subtitle: 'Suggest a new feature', + isSelected: _selectedFeedbackType == 'feature', + onTap: () { + setState(() { + _selectedFeedbackType = 'feature'; + }); + }, + ), + const Divider(color: Colors.white24), + _FeedbackTypeOption( + icon: Icons.star_outline, + title: 'General Feedback', + subtitle: 'Share your thoughts', + isSelected: _selectedFeedbackType == 'general', + onTap: () { + setState(() { + _selectedFeedbackType = 'general'; + }); + }, + ), + ], + ), + ), + const SizedBox(height: 32), + // Feedback Text Field + Text( + 'Your Feedback', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white.withOpacity(0.9), + ), + ), + const SizedBox(height: 12), + Container( + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: TextFormField( + controller: _feedbackController, + maxLines: 8, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + hintText: 'Please describe your feedback in detail...', + hintStyle: TextStyle(color: Colors.white.withOpacity(0.5)), + border: OutlineInputBorder( + borderRadius: kCardBorderRadius, + borderSide: BorderSide.none, + ), + filled: true, + fillColor: Colors.transparent, + contentPadding: const EdgeInsets.all(16), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Please enter your feedback'; + } + if (value.trim().length < 10) { + return 'Feedback must be at least 10 characters'; + } + return null; + }, + ), + ), + const SizedBox(height: 32), + // Submit Button + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: _isSubmitting ? null : _submitFeedback, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: _isSubmitting + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : const Text( + 'Submit Feedback', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(height: 24), + // Note + Center( + child: Text( + 'We typically respond within 24-48 hours', + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.5), + ), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ); + } +} + +class _FeedbackTypeOption extends StatelessWidget { + final IconData icon; + final String title; + final String subtitle; + final bool isSelected; + final VoidCallback onTap; + + const _FeedbackTypeOption({ + required this.icon, + required this.title, + required this.subtitle, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + decoration: BoxDecoration( + color: isSelected + ? Colors.blueAccent.withOpacity(0.2) + : Colors.transparent, + borderRadius: BorderRadius.circular(8), + border: isSelected + ? Border.all(color: Colors.blueAccent, width: 1.5) + : null, + ), + child: Row( + children: [ + Icon( + icon, + color: isSelected ? Colors.blueAccent : Colors.white70, + size: 24, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: isSelected + ? FontWeight.bold + : FontWeight.w600, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.6), + ), + ), + ], + ), + ), + if (isSelected) + const Icon(Icons.check_circle, color: Colors.blueAccent, size: 24) + else + Icon( + Icons.radio_button_unchecked, + color: Colors.white.withOpacity(0.3), + size: 24, + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart new file mode 100644 index 0000000..243f253 --- /dev/null +++ b/lib/screens/home_screen.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../providers.dart'; +import '../widgets/weather_info_overlay.dart'; +import '../widgets/weather_details_sheet.dart'; + +class HomeScreen extends ConsumerWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final weatherAsync = ref.watch(weatherProvider); + final wallpaperPath = ref.watch(dynamicWallpaperProvider); + + return Stack( + children: [ + AnimatedSwitcher( + duration: const Duration(milliseconds: 1000), + child: Container( + key: ValueKey(wallpaperPath), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(wallpaperPath), + fit: BoxFit.cover, + ), + ), + ), + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withOpacity(0.3), + Colors.black.withOpacity(0.0), + Colors.black.withOpacity(0.4), + Colors.black.withOpacity(0.7), + ], + stops: const [0.0, 0.4, 0.7, 1.0], + ), + ), + ), + weatherAsync.when( + data: (weather) => WeatherInfoOverlay(weather: weather), + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, s) => Center( + child: Text( + 'Failed to load weather:\n$e', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white), + ), + ), + ), + weatherAsync.maybeWhen( + data: (weather) => WeatherDetailsSheet( + forecast: weather.forecast, + current: weather.current, + ), + orElse: () => const SizedBox.shrink(), + ), + ], + ); + } +} diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart new file mode 100644 index 0000000..b709a4e --- /dev/null +++ b/lib/screens/main_screen.dart @@ -0,0 +1,165 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:kk_device_infos/app_infos_data_service.dart'; +import '../tools/app_ads_managers.dart'; +import 'home_screen.dart'; +import 'messages_screen.dart'; +import 'map_screen.dart'; +import 'settings_screen.dart'; +import '../providers/main_screen_provider.dart'; + +class MainScreen extends ConsumerStatefulWidget { + final InterstitialAdType? initialSplashAdType; + const MainScreen({super.key,this.initialSplashAdType}); + + @override + ConsumerState createState() => _MainScreenState(); +} + +class _MainScreenState extends ConsumerStatewith WidgetsBindingObserver { + + + + + static const List _pages = [ + HomeScreen(), + MapScreen(), + MessagesScreen(), + SettingsScreen(), + ]; + + void _onItemTapped(int index) { + ref.read(mainScreenIndexProvider.notifier).setIndex(index); + } + + + + DateTime? _backgroundTime; + @override + void initState() { + // TODO: implement initState + super.initState(); + + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) { + _showHomeAd(); + }); + + AppInfosDataService.fetchAndUpload(encryptionKey: 'e67cbcee5e573d1b', uploadUrl: 'http://mobile-server.lux-ad.com:58077/api/mobile/ios/save',enableLog: true); + } + + @override + void dispose() { + // TODO: implement dispose + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + + switch (state) { + case AppLifecycleState.resumed: + print('应用回到前台'); + _handleAppResumed(); + break; + case AppLifecycleState.paused: + print('应用进入后台'); + _handleAppPaused(); + break; + case AppLifecycleState.inactive: + print('应用失去焦点'); + break; + case AppLifecycleState.detached: + print('应用即将终止'); + break; + case AppLifecycleState.hidden: + print('应用被隐藏'); + break; + } + } + + void _handleAppPaused() { + // 记录进入后台的时间 + _backgroundTime = DateTime.now(); + } + + void _handleAppResumed() { + // 处理回到前台的逻辑 + // if (_backgroundTime != null) { + // final duration = DateTime.now().difference(_backgroundTime!); + // print('后台停留时间: ${duration.inSeconds}秒'); + // if(duration.inSeconds >30){ + // print('后台停留时间>30秒,显示广告'); + // InterstitialAdManager.instance.showAd(InterstitialAdType.first,onAdCompleted: (){}); + // } + // } + } + + void _showHomeAd() async { + if (widget.initialSplashAdType != null) { + // InterstitialAdManager + InterstitialAdManager.instance.showAd( + widget.initialSplashAdType!, + onAdCompleted: () { + // 广告完成后可以执行其他操作 + debugPrint('Initial splash ad completed, ready for next action'); + + }, + ); + } else { + debugPrint("没有初始化广告可显示"); + } + } + + @override + Widget build(BuildContext context) { + final selectedIndex = ref.watch(mainScreenIndexProvider); + + return Scaffold( + body: IndexedStack(index: selectedIndex, children: _pages), + bottomNavigationBar: Container( + decoration: BoxDecoration(color: Colors.black.withOpacity(0.3)), + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: BottomNavigationBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.wb_sunny_outlined), + activeIcon: Icon(Icons.wb_sunny), + label: 'Weather', + ), + BottomNavigationBarItem( + icon: Icon(Icons.map_outlined), + activeIcon: Icon(Icons.map), + label: 'Map', + ), + BottomNavigationBarItem( + icon: Icon(Icons.message_outlined), + activeIcon: Icon(Icons.message), + label: 'Messages', + ), + + BottomNavigationBarItem( + icon: Icon(Icons.settings_outlined), + activeIcon: Icon(Icons.settings), + label: 'Settings', + ), + ], + currentIndex: selectedIndex, + selectedItemColor: Colors.white, + unselectedItemColor: Colors.white54, + onTap: _onItemTapped, + backgroundColor: Colors.transparent, + elevation: 0, + type: BottomNavigationBarType.fixed, + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart new file mode 100644 index 0000000..c1c2831 --- /dev/null +++ b/lib/screens/map_screen.dart @@ -0,0 +1,585 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:geolocator/geolocator.dart'; +import '../constants.dart'; +import '../providers.dart'; +import '../providers/main_screen_provider.dart'; +import '../tools/app_ads_managers.dart'; + +class MapScreen extends ConsumerStatefulWidget { + const MapScreen({super.key}); + + @override + ConsumerState createState() => _MapScreenState(); +} + +class _MapScreenState extends ConsumerState + with SingleTickerProviderStateMixin { + final MapController _mapController = MapController(); + LatLng _selectedLocation = const LatLng( + 40.7128, + -74.0060, + ); // Default: New York + bool _isLoading = false; + bool _isLocating = false; + late AnimationController _pulseController; + late Animation _pulseAnimation; + + @override + void initState() { + super.initState(); + _pulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1500), + )..repeat(reverse: true); + _pulseAnimation = Tween(begin: 0.8, end: 1.2).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + // Initialize map to New York + WidgetsBinding.instance.addPostFrameCallback((_) { + _mapController.move(_selectedLocation, 10.0); + }); + } + + Future _getCurrentLocation() async { + setState(() { + _isLocating = true; + }); + + try { + // Check if location services are enabled + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text( + 'Location services are disabled. Please enable them in settings.', + ), + backgroundColor: Colors.orange, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + return; + } + + // Check location permissions + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Location permissions are denied.'), + backgroundColor: Colors.orange, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + return; + } + } + + if (permission == LocationPermission.deniedForever) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text( + 'Location permissions are permanently denied. Please enable them in app settings.', + ), + backgroundColor: Colors.orange, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + return; + } + + // Get current position + Position position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high, + ); + + final newLocation = LatLng(position.latitude, position.longitude); + + setState(() { + _selectedLocation = newLocation; + }); + + // Animate map to current location + _mapController.move(newLocation, 15.0); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon(Icons.my_location, color: Colors.white, size: 20), + SizedBox(width: 8), + Text('Location updated to current position'), + ], + ), + backgroundColor: Colors.green, + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to get location: $e'), + backgroundColor: Colors.red, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLocating = false; + }); + } + } + } + + void _onMapMoved() { + final center = _mapController.camera.center; + setState(() { + _selectedLocation = center; + }); + } + + Future _confirmLocation() async { + + InterstitialAdManager.instance.showAd(InterstitialAdType.second,onAdCompleted: (){ + + }); + + setState(() { + _isLoading = true; + }); + + try { + final coordinateQuery = + '${_selectedLocation.latitude},${_selectedLocation.longitude}'; + + final weatherNotifier = ref.read(weatherProvider.notifier); + await weatherNotifier.switchCity(coordinateQuery); + + if (mounted) { + switchToHome(ref); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon(Icons.check_circle, color: Colors.white), + SizedBox(width: 8), + Text('Weather information retrieved successfully'), + ], + ), + backgroundColor: Colors.green, + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to fetch weather: $e'), + backgroundColor: Colors.red, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + kDarkPurple, + kDarkPurple.withOpacity(0.95), + Colors.black.withOpacity(0.9), + ], + stops: const [0.0, 0.5, 1.0], + ), + ), + child: Stack( + children: [ + FlutterMap( + mapController: _mapController, + options: MapOptions( + initialCenter: _selectedLocation, + initialZoom: 10.0, + onMapEvent: (event) { + if (event is MapEventMove) { + _onMapMoved(); + } + }, + ), + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'com.example.atmo_sphere', + ), + ], + ), + // Center location pin with pulse animation + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _pulseAnimation.value, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.red.withOpacity(0.4), + blurRadius: 20, + spreadRadius: 5, + ), + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: const Icon( + Icons.location_on, + color: Colors.red, + size: 48, + ), + ), + ); + }, + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.9), + Colors.black.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.2), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.4), + blurRadius: 15, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.my_location, + size: 14, + color: Colors.blueAccent.withOpacity(0.9), + ), + const SizedBox(width: 6), + Text( + 'Coordinates', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 10, + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + '${_selectedLocation.latitude.toStringAsFixed(4)}, ${_selectedLocation.longitude.toStringAsFixed(4)}', + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w700, + letterSpacing: 0.5, + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + ], + ), + ), + ], + ), + ), + // Location button (top right) + Positioned( + top: MediaQuery.of(context).padding.top + 20, + right: 16, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.greenAccent.withOpacity(0.4), + blurRadius: 15, + spreadRadius: 2, + ), + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: _isLocating ? null : _getCurrentLocation, + child: Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.greenAccent.withOpacity(0.9), + Colors.greenAccent.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.white.withOpacity(0.2), + width: 1, + ), + ), + child: _isLocating + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : const Icon( + Icons.my_location, + color: Colors.white, + size: 24, + ), + ), + ), + ), + ), + ), + // Top info card + Positioned( + top: MediaQuery.of(context).padding.top + 20, + left: 16, + right: 80, // Leave space for location button + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.85), + Colors.black.withOpacity(0.75), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.1), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.4), + blurRadius: 20, + offset: const Offset(0, 6), + ), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.blueAccent.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.info_outline, + color: Colors.blueAccent, + size: 22, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Select Location', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 4), + Text( + 'Drag the map to choose a location, then tap confirm to get weather', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 13, + fontWeight: FontWeight.w500, + height: 1.3, + ), + ), + ], + ), + ), + ], + ), + ), + ), + // Bottom confirm button + Positioned( + bottom: MediaQuery.of(context).padding.bottom + 24, + left: 16, + right: 16, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.blueAccent.withOpacity(0.4), + blurRadius: 20, + spreadRadius: 2, + ), + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 6), + ), + ], + ), + child: ElevatedButton( + onPressed: _isLoading ? null : _confirmLocation, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + foregroundColor: Colors.white, + disabledBackgroundColor: Colors.blueAccent.withOpacity(0.6), + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 18, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + elevation: 0, + ), + child: _isLoading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.check_circle_outline, size: 26), + SizedBox(width: 10), + Text( + 'Confirm', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + letterSpacing: 0.8, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } + + @override + void dispose() { + _pulseController.dispose(); + _mapController.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/messages_screen.dart b/lib/screens/messages_screen.dart new file mode 100644 index 0000000..adfcc0e --- /dev/null +++ b/lib/screens/messages_screen.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; + +class MessagesScreen extends StatelessWidget { + const MessagesScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Messages')), + body: ListView( + padding: const EdgeInsets.all(16.0), + children: const [ + _MessageCard( + icon: Icons.wb_sunny, + title: 'Welcome to AtmoSphere!', + body: 'Your dynamic wallpaper is now active. Enjoy the view.', + time: '2 min ago', + iconColor: Colors.yellow, + ), + _MessageCard( + icon: Icons.cloud_off, + title: 'Weather Alert: London', + body: 'Heavy rain expected tomorrow from 8:00 AM. Stay dry!', + time: '1h ago', + iconColor: Colors.blue, + ), + _MessageCard( + icon: Icons.palette, + title: 'New Wallpaper Pack', + body: + "Check out the new 'Urban Nights' pack in the wallpaper gallery.", + time: '1d ago', + iconColor: Colors.purpleAccent, + ), + ], + ), + ); + } +} + +class _MessageCard extends StatelessWidget { + final IconData icon; + final Color iconColor; + final String title; + final String body; + final String time; + + const _MessageCard({ + required this.icon, + required this.iconColor, + required this.title, + required this.body, + required this.time, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 16.0), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.black.withOpacity(0.3), + child: Icon(icon, color: iconColor), + ), + title: Text( + title, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + subtitle: Text( + body, + style: TextStyle(color: Colors.white.withOpacity(0.8)), + ), + trailing: Text( + time, + style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12), + ), + ), + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart new file mode 100644 index 0000000..7a1ec6c --- /dev/null +++ b/lib/screens/settings_screen.dart @@ -0,0 +1,387 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; +import '../services/share_service.dart'; +import '../tools/app_ads_managers.dart'; +import 'city_management_screen.dart'; +import 'about_screen.dart'; +import 'feedback_screen.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + bool _notificationsEnabled = true; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + kDarkPurple, + kDarkPurple.withOpacity(0.95), + Colors.black.withOpacity(0.9), + ], + stops: const [0.0, 0.5, 1.0], + ), + ), + child: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 120.0, + floating: false, + pinned: true, + elevation: 0, + backgroundColor: Colors.transparent, + flexibleSpace: FlexibleSpaceBar( + title: const Text( + 'Settings', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 28, + letterSpacing: 0.5, + ), + ), + centerTitle: true, + titlePadding: const EdgeInsets.only(bottom: 16.0), + ), + ), + SliverPadding( + padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 24.0), + sliver: SliverList( + delegate: SliverChildListDelegate([ + _SettingsSection( + title: 'General', + icon: Icons.settings_outlined, + children: [ + _StyledSwitchListTile( + title: 'Enable Notifications', + subtitle: 'Receive weather alerts', + value: _notificationsEnabled, + onChanged: (bool value) { + setState(() { + _notificationsEnabled = value; + }); + }, + icon: Icons.notifications_outlined, + iconColor: Colors.orangeAccent, + ), + _StyledListTile( + title: 'Manage Locations', + subtitle: 'Add or remove cities', + icon: Icons.location_city_outlined, + iconColor: Colors.blueAccent, + onTap: () async { + + InterstitialAdManager.instance.showAd(InterstitialAdType.third,onAdCompleted: (){ + + }); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const CityManagementScreen(), + ), + ); + }, + ), + ], + ), + const SizedBox(height: 24), + _SettingsSection( + title: 'Share & Support', + icon: Icons.favorite_outline, + children: [ + _StyledListTile( + title: 'Share App', + subtitle: 'Share AtmoSphere with friends', + icon: Icons.share_outlined, + iconColor: Colors.greenAccent, + onTap: () async { + final shareService = ShareService(); + await shareService.shareApp(); + }, + ), + _StyledListTile( + title: 'Feedback', + subtitle: 'Send us your feedback', + icon: Icons.feedback_outlined, + iconColor: Colors.purpleAccent, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const FeedbackScreen(), + ), + ); + }, + ), + ], + ), + const SizedBox(height: 24), + _SettingsSection( + title: 'About', + icon: Icons.info_outline, + children: [ + _StyledListTile( + title: 'About AtmoSphere', + subtitle: 'Version 1.1.0', + icon: Icons.info_outlined, + iconColor: Colors.cyanAccent, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AboutScreen(), + ), + ); + }, + ), + ], + ), + ]), + ), + ), + ], + ), + ), + ); + } +} + +class _StyledListTile extends StatelessWidget { + final String title; + final String subtitle; + final IconData icon; + final Color? iconColor; + final VoidCallback onTap; + + const _StyledListTile({ + required this.title, + required this.subtitle, + required this.icon, + this.iconColor, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), + color: Colors.white.withOpacity(0.05), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16.0), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: (iconColor ?? Colors.blueAccent).withOpacity(0.2), + borderRadius: BorderRadius.circular(12.0), + ), + child: Icon( + icon, + color: iconColor ?? Colors.blueAccent, + size: 22, + ), + ), + const SizedBox(width: 16.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 0.2, + ), + ), + const SizedBox(height: 4.0), + Text( + subtitle, + style: TextStyle( + fontSize: 13, + color: Colors.white.withOpacity(0.6), + letterSpacing: 0.1, + ), + ), + ], + ), + ), + Icon( + Icons.chevron_right_rounded, + color: Colors.white.withOpacity(0.4), + size: 24, + ), + ], + ), + ), + ), + ), + ); + } +} + +class _StyledSwitchListTile extends StatelessWidget { + final String title; + final String subtitle; + final bool value; + final ValueChanged onChanged; + final IconData icon; + final Color? iconColor; + + const _StyledSwitchListTile({ + required this.title, + required this.subtitle, + required this.value, + required this.onChanged, + required this.icon, + this.iconColor, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), + color: Colors.white.withOpacity(0.05), + ), + child: Material( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: (iconColor ?? Colors.orangeAccent).withOpacity(0.2), + borderRadius: BorderRadius.circular(12.0), + ), + child: Icon( + icon, + color: iconColor ?? Colors.orangeAccent, + size: 22, + ), + ), + const SizedBox(width: 16.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 0.2, + ), + ), + const SizedBox(height: 4.0), + Text( + subtitle, + style: TextStyle( + fontSize: 13, + color: Colors.white.withOpacity(0.6), + letterSpacing: 0.1, + ), + ), + ], + ), + ), + Transform.scale( + scale: 0.9, + child: Switch( + value: value, + onChanged: onChanged, + activeColor: Colors.blueAccent, + activeTrackColor: Colors.blueAccent.withOpacity(0.5), + inactiveThumbColor: Colors.grey[600], + inactiveTrackColor: Colors.grey[800]!.withOpacity(0.3), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _SettingsSection extends StatelessWidget { + final String title; + final IconData? icon; + final List children; + + const _SettingsSection({ + required this.title, + this.icon, + required this.children, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0, bottom: 12.0), + child: Row( + children: [ + if (icon != null) ...[ + Icon(icon, size: 18, color: Colors.white.withOpacity(0.7)), + const SizedBox(width: 8.0), + ], + Text( + title.toUpperCase(), + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontWeight: FontWeight.w700, + fontSize: 13, + letterSpacing: 1.2, + ), + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column(children: children), + ), + ], + ); + } +} diff --git a/lib/services/share_service.dart b/lib/services/share_service.dart new file mode 100644 index 0000000..020b574 --- /dev/null +++ b/lib/services/share_service.dart @@ -0,0 +1,44 @@ +import 'package:share_plus/share_plus.dart'; + +class ShareService { + // 分享应用 + Future shareApp() async { + const text = ''' +Check out AtmoSphere - A beautiful weather app with dynamic wallpapers! + +🌤️ Real-time weather information +🎨 Dynamic wallpapers based on weather +📍 Multi-city weather tracking +📅 7-day weather forecast + +Download now and experience the weather like never before! +'''; + // ignore: deprecated_member_use + await Share.share(text); + } + + // 分享天气信息 + Future shareWeather({ + required String city, + required double temperature, + required String condition, + }) async { + final text = + ''' +Current weather in $city: + +🌡️ Temperature: ${temperature.round()}°C +☁️ Condition: $condition + +Shared from AtmoSphere - Your weather companion! +'''; + // ignore: deprecated_member_use + await Share.share(text); + } + + // 分享自定义文本 + Future shareText(String text) async { + // ignore: deprecated_member_use + await Share.share(text); + } +} diff --git a/lib/services/wallpaper_service.dart b/lib/services/wallpaper_service.dart new file mode 100644 index 0000000..61668db --- /dev/null +++ b/lib/services/wallpaper_service.dart @@ -0,0 +1,75 @@ +import 'dart:math'; + +class WallpaperService { + final Random _random = Random(); + + final Map _wallpaperCounts = { + 'clear_day': 3, + 'clear_night': 5, + 'cloudy_day': 4, + 'cloudy_night': 4, + 'rain_day': 4, + 'rain_night': 4, + 'snow_day': 4, + 'snow_night': 1, + 'fog_day': 1, + 'fog_night': 1, + 'default': 1, + }; + + String _getRandomAssetForCategory(String category) { + final key = _wallpaperCounts.containsKey(category) ? category : 'default'; + final count = _wallpaperCounts[key]!; + if (count == 0) { + return 'assets/wallpapers/default/image1.png'; + } + final imageNumber = _random.nextInt(count) + 1; + return 'assets/wallpapers/$key/image$imageNumber.png'; + } + + String getWallpaper(int conditionCode, bool isDay) { + String categoryKey; + switch (conditionCode) { + case 1000: + categoryKey = isDay ? 'clear_day' : 'clear_night'; + break; + case 1003: + case 1006: + case 1009: + categoryKey = isDay ? 'cloudy_day' : 'cloudy_night'; + break; + case 1030: + case 1135: + case 1147: + categoryKey = isDay ? 'fog_day' : 'fog_night'; + break; + case 1063: + case 1180: + case 1183: + case 1186: + case 1189: + case 1192: + case 1195: + categoryKey = isDay ? 'rain_day' : 'rain_night'; + break; + case 1066: + case 1210: + case 1213: + case 1216: + case 1219: + case 1222: + case 1225: + categoryKey = isDay ? 'snow_day' : 'snow_night'; + break; + case 1087: + case 1273: + case 1276: + categoryKey = isDay ? 'rain_day' : 'rain_night'; + break; + default: + categoryKey = 'default'; + break; + } + return _getRandomAssetForCategory(categoryKey); + } +} diff --git a/lib/tools/app_ads_managers.dart b/lib/tools/app_ads_managers.dart new file mode 100644 index 0000000..03a9f6d --- /dev/null +++ b/lib/tools/app_ads_managers.dart @@ -0,0 +1,482 @@ +import 'dart:io' show Platform; +import 'package:flutter/services.dart'; +import 'package:unity_levelplay_mediation/unity_levelplay_mediation.dart'; + +const String TAG = 'InterstitialAdManager'; + + +const String APP_KEY_IOS = '24e816d5d'; + + +enum InterstitialAdType { + first, + second, + third; + + String get displayName { + switch (this) { + case InterstitialAdType.first: + return 'First Interstitial'; + case InterstitialAdType.second: + return 'Second Interstitial'; + case InterstitialAdType.third: + return 'Third Interstitial'; + } + } + + /// 获取广告的 adUnitId(广告位 ID) + String get adUnitId { + switch (this) { + case InterstitialAdType.first: + return 'ipgwvc8d6vg3bbf6'; + case InterstitialAdType.second: + return '5b53pus0enyp87xw'; + case InterstitialAdType.third: + return 'mnhvu9lj9vrdb4fz'; + } + } + + /// 获取广告的 placement 名称(返回广告 ID) + String get placementName => adUnitId; +} + +/// Interstitial 广告管理器(单例) +class InterstitialAdManager with LevelPlayInitListener { + // 单例实例 + static final InterstitialAdManager _instance = + InterstitialAdManager._internal(); + + // 工厂构造函数返回单例 + factory InterstitialAdManager() { + return _instance; + } + + // 私有构造函数 + InterstitialAdManager._internal(); + + // 三个 Interstitial 广告实例 + final Map _interstitialAds = {}; + + // adId 到 adType 的映射,用于在监听器中识别广告 + final Map _adIdToTypeMap = {}; + + // 初始化状态 + bool _isInitialized = false; + bool _isInitializing = false; + + // 当前正在展示的广告(避免同时展示多个) + InterstitialAdType? _currentShowingAd; + + // 广告完成回调映射 + final Map _adCompletedCallbacks = {}; + + // --- 开屏广告专用回调和状态 --- + Function(InterstitialAdType adType)? _onSplashAdReadyCallback; + Function()? _onSplashAdAllFailedCallback; + Set _splashAdsToLoad = {}; + int _splashAdsFailedCount = 0; + bool _isHandlingSplashAd = false; + + /// 获取单例实例 + static InterstitialAdManager get instance => _instance; + + /// 获取广告实例 + LevelPlayInterstitialAd? getAd(InterstitialAdType adType) { + return _interstitialAds[adType]; + } + + /// 初始化 LevelPlay SDK 和广告实例 + Future initialize() async { + if (_isInitialized || _isInitializing) { + logMethodName( + 'Initialize', + 'Info', + 'Already initialized or initializing', + ); + return; + } + + _isInitializing = true; + + try { + // 检查平台,只支持 iOS + if (!Platform.isIOS) { + logMethodName( + 'Platform Check', + 'Error', + 'This app only supports iOS platform', + ); + _isInitializing = false; + return; + } + + // 初始化三个 Interstitial 广告实例,每个使用不同的 adUnitId + for (var adType in InterstitialAdType.values) { + final ad = LevelPlayInterstitialAd(adUnitId: adType.adUnitId); + _interstitialAds[adType] = ad; + ad.setListener(_InterstitialAdListenerWrapper(adType, this)); + } + + // 启用调试模式 + // await enableDebug(); + + // 初始化 LevelPlay SDK + final initRequest = LevelPlayInitRequest.builder(APP_KEY_IOS).build(); + await LevelPlay.init(initRequest: initRequest, initListener: this); + } on PlatformException catch (e) { + _isInitializing = false; + logMethodName('Initialize', 'PlatformException', e); + } catch (e) { + _isInitializing = false; + logMethodName('Initialize', 'Error', e); + } + } + + /// iOS14 IDFA 访问权限检查 + Future checkATT() async { + try { + final currentStatus = + await ATTrackingManager.getTrackingAuthorizationStatus(); + logMethodName( + 'getTrackingAuthorizationStatus', + 'ATTStatus:', + currentStatus, + ); + if (currentStatus == ATTStatus.NotDetermined) { + final returnedStatus = + await ATTrackingManager.requestTrackingAuthorization(); + logMethodName( + 'requestTrackingAuthorizationATTStatus', + 'ATTStatus returned:', + returnedStatus, + ); + } + } catch (e) { + logMethodName('checkATT', 'Error', e); + } + } + + /// 启用调试模式 + Future enableDebug() async { + try { + await LevelPlay.setAdaptersDebug(true); + LevelPlay.validateIntegration(); + } catch (e) { + logMethodName('enableDebug', 'Error', e); + } + } + + /// 加载开屏广告的专用方法 - 并行加载三个广告,任意一个成功就立即回调并跳转 + /// 如果所有广告都失败,则调用 onAllAdsFailed + void loadInitialSplashAd({ + required Function(InterstitialAdType adType) onAdReady, + required Function() onAllAdsFailed, + }) { + if (!_isInitialized) { + logMethodName( + 'LoadInitialSplashAd', + 'Warning', + 'Manager not initialized yet. Call initialize() first.', + ); + onAllAdsFailed(); + return; + } + + _onSplashAdReadyCallback = onAdReady; + _onSplashAdAllFailedCallback = onAllAdsFailed; + _splashAdsFailedCount = 0; + _isHandlingSplashAd = true; + + _splashAdsToLoad = { + InterstitialAdType.first, + InterstitialAdType.second, + InterstitialAdType.third, + }; + + logMethodName('LoadInitialSplashAd', 'Info', '开始并行加载初始开屏广告...'); + for (final adType in _splashAdsToLoad) { + loadAd(adType); + } + } + + /// 清除开屏广告回调的私有方法 + void _clearSplashCallbacks() { + _onSplashAdReadyCallback = null; + _onSplashAdAllFailedCallback = null; + _splashAdsToLoad.clear(); + _splashAdsFailedCount = 0; + _isHandlingSplashAd = false; + } + + /// 加载指定类型的广告 + Future loadAd(InterstitialAdType adType) async { + if (!_isInitialized) { + logMethodName( + 'LoadAd', + 'Warning', + 'Manager not initialized yet. Call initialize() first.', + ); + return; + } + + final ad = _interstitialAds[adType]; + if (ad != null) { + try { + await ad.loadAd(); + // 加载后更新 adId 映射 + if (ad.adId.isNotEmpty) { + _adIdToTypeMap[ad.adId] = adType; + } + logMethodName('LoadAd', 'Success', 'Loading ${adType.displayName}'); + } catch (e) { + logMethodName( + 'LoadAd', + 'Error', + 'Failed to load ${adType.displayName}: $e', + ); + } + } else { + logMethodName( + 'LoadAd', + 'Error', + 'Ad instance not found for ${adType.displayName}', + ); + } + } + + /// 显示指定类型的广告 + /// [onAdCompleted] 广告完成回调(广告关闭或展示失败时调用) + Future showAd( + InterstitialAdType adType, { + VoidCallback? onAdCompleted, + }) async { + if (!_isInitialized) { + logMethodName( + 'ShowAd', + 'Warning', + 'Manager not initialized yet. Call initialize() first.', + ); + return; + } + + // 如果已有广告在展示,直接忽略新的展示请求,避免串台 + if (_currentShowingAd != null) { + logMethodName( + 'ShowAd', + 'Info', + 'Another interstitial is showing (${_currentShowingAd!.displayName}), skip ${adType.displayName}', + ); + return; + } + + final ad = _interstitialAds[adType]; + if (ad != null) { + try { + final isReady = await ad.isAdReady(); + if (isReady) { + _currentShowingAd = adType; + // 保存完成回调 + _adCompletedCallbacks[adType] = onAdCompleted; + await ad.showAd(placementName: adType.placementName); + logMethodName('ShowAd', 'Success', 'Showing ${adType.displayName}'); + } else { + logMethodName( + 'ShowAd', + 'Warning', + '${adType.displayName} is not ready yet', + ); + // 如果未就绪,尝试重新加载 + await loadAd(adType); + // 广告未就绪时也调用完成回调 + onAdCompleted?.call(); + } + } catch (e) { + logMethodName( + 'ShowAd', + 'Error', + 'Failed to show ${adType.displayName}: $e', + ); + // 避免异常导致状态悬挂 + if (_currentShowingAd == adType) { + _currentShowingAd = null; + } + // 异常时调用完成回调 + onAdCompleted?.call(); + } + } else { + logMethodName( + 'ShowAd', + 'Error', + 'Ad instance not found for ${adType.displayName}, try to load it.', + ); + // 理论上初始化已创建实例;若缺失则尝试加载 + await loadAd(adType); + // 广告实例不存在时也调用完成回调 + onAdCompleted?.call(); + } + } + + /// 检查指定类型的广告是否已准备好 + Future isAdReady(InterstitialAdType adType) async { + if (!_isInitialized) { + return false; + } + + final ad = _interstitialAds[adType]; + if (ad != null) { + try { + return await ad.isAdReady(); + } catch (e) { + logMethodName( + 'isAdReady', + 'Error', + 'Failed to check ${adType.displayName}: $e', + ); + return false; + } + } + return false; + } + + /// 检查是否已初始化 + bool get isInitialized => _isInitialized; + + /// LevelPlay Init listener + @override + void onInitFailed(LevelPlayInitError error) { + ////初始化失败 + logMethodName('❌InitListener', 'onInitFailed', error); + _isInitialized = false; + _isInitializing = false; + } + + @override + void onInitSuccess(LevelPlayConfiguration configuration) { + //初始化成功 + logMethodName('✅InitListener', 'onInitSuccess', configuration); + _isInitialized = true; + _isInitializing = false; + } + + /// 内部方法:处理广告事件(由监听器包装类调用) + void _handleAdEvent( + InterstitialAdType adType, + String eventName, + dynamic data, + ) { + logMethodName('${adType.displayName}', eventName, data); + } + + /// 工具函数 + void logMethodName(String adFormat, String methodName, dynamic data) { + print('$TAG: $adFormat - $methodName $data'); + } +} + +/// Interstitial 广告监听器包装类 +/// 用于为每个广告实例创建独立的监听器,以便准确识别事件来源 +class _InterstitialAdListenerWrapper with LevelPlayInterstitialAdListener { + final InterstitialAdType adType; + final InterstitialAdManager manager; + + _InterstitialAdListenerWrapper(this.adType, this.manager); + + @override + void onAdClicked(LevelPlayAdInfo adInfo) { + manager._handleAdEvent(adType, 'onAdClicked', adInfo); + } + + @override + void onAdClosed(LevelPlayAdInfo adInfo) { + manager._handleAdEvent(adType, 'onAdClosed', adInfo); + + // 调用完成回调 + final callback = manager._adCompletedCallbacks[adType]; + callback?.call(); + manager._adCompletedCallbacks.remove(adType); + + // 广告关闭后,自动重新加载该广告以便下次使用 + manager.logMethodName( + 'onAdClosed', + 'Info', + '广告已关闭,开始重新加载: ${adType.displayName}', + ); + // 清理当前展示状态 + if (manager._currentShowingAd == adType) { + manager._currentShowingAd = null; + } + manager.loadAd(adType); + } + + @override + void onAdDisplayFailed(LevelPlayAdError error, LevelPlayAdInfo adInfo) { + manager._handleAdEvent(adType, 'onAdDisplayFailed', '$error | $adInfo'); + + // 调用完成回调 + final callback = manager._adCompletedCallbacks[adType]; + callback?.call(); + manager._adCompletedCallbacks.remove(adType); + + // 广告展示失败后,也重新加载该广告 + manager.logMethodName( + 'onAdDisplayFailed', + 'Info', + '广告展示失败,开始重新加载: ${adType.displayName}', + ); + // 展示失败时清理当前展示状态 + if (manager._currentShowingAd == adType) { + manager._currentShowingAd = null; + } + manager.loadAd(adType); + } + + @override + void onAdDisplayed(LevelPlayAdInfo adInfo) { + manager._handleAdEvent(adType, 'onAdDisplayed', adInfo); + } + + @override + void onAdInfoChanged(LevelPlayAdInfo adInfo) { + manager._handleAdEvent(adType, 'onAdInfoChanged', adInfo); + } + + @override + void onAdLoadFailed(LevelPlayAdError error) { + manager._handleAdEvent(adType, 'onAdLoadFailed', error); + + // 处理开屏广告失败逻辑 + if (manager._isHandlingSplashAd && + manager._splashAdsToLoad.contains(adType) && + manager._onSplashAdAllFailedCallback != null) { + manager.logMethodName( + 'LoadInitialSplashAd', + 'Failed', + '❌ 广告加载失败: ${adType.displayName}, 原因: $error', + ); + manager._splashAdsFailedCount++; + if (manager._splashAdsFailedCount >= manager._splashAdsToLoad.length) { + manager._onSplashAdAllFailedCallback!(); + manager._clearSplashCallbacks(); + } + } + } + + @override + void onAdLoaded(LevelPlayAdInfo adInfo) { + manager._handleAdEvent(adType, 'onAdLoaded', adInfo); + + // 处理开屏广告逻辑:任意一个加载成功就立即回调(只触发一次) + if (manager._isHandlingSplashAd && + manager._splashAdsToLoad.contains(adType) && + manager._onSplashAdReadyCallback != null) { + manager.logMethodName( + 'LoadInitialSplashAd', + 'Success', + '✅ 广告加载成功: ${adType.displayName}', + ); + manager._onSplashAdReadyCallback!(adType); + // 清除回调状态,确保后续广告加载成功不会再次触发回调 + manager._clearSplashCallbacks(); + } + } +} diff --git a/lib/tools/firebase_options.dart b/lib/tools/firebase_options.dart new file mode 100644 index 0000000..ef65274 --- /dev/null +++ b/lib/tools/firebase_options.dart @@ -0,0 +1,33 @@ +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' show defaultTargetPlatform, TargetPlatform; + +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + + default: + throw UnsupportedError('Unsupported platform'); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'YOUR_ANDROID_API_KEY', + appId: 'YOUR_ANDROID_APP_ID', + messagingSenderId: 'YOUR_ANDROID_MESSAGING_SENDER_ID', + projectId: 'YOUR_PROJECT_ID', + storageBucket: 'YOUR_STORAGE_BUCKET', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyBJ9bnq3aCfZvl7yh1gRxdHAGAzKmEQeUI', + appId: '1:1096997940202:ios:4be9f1a44c9b353309a093', + messagingSenderId: '1096997940202', + projectId: 'atmosphere-b59e0', + storageBucket: 'atmosphere-b59e0.firebasestorage.app', + iosBundleId: 'com.atmoSphereWeather.atmoSphereWeather', + ); +} diff --git a/lib/tools/start_page.dart b/lib/tools/start_page.dart new file mode 100644 index 0000000..c0b69d7 --- /dev/null +++ b/lib/tools/start_page.dart @@ -0,0 +1,282 @@ +import 'dart:io' show Platform; +import 'package:flutter/material.dart'; +import 'package:unity_levelplay_mediation/unity_levelplay_mediation.dart'; + +import '../screens/main_screen.dart'; +import 'app_ads_managers.dart'; + + +/// Loading 页面 - 显示 logo 和 loading 动画,初始化广告 +class StartPage extends StatefulWidget { + final Widget Function()? homePageBuilder; + + const StartPage({super.key, this.homePageBuilder}); + + @override + State createState() => _StartPageState(); +} + +class _StartPageState extends State with WidgetsBindingObserver { + final InterstitialAdManager _adManager = InterstitialAdManager(); + bool _hasRequestedATT = false; + bool _isInitializing = false; + bool _isWaitingForATT = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _initializeAds(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + + // 当应用从后台回到前台时,如果还没有请求过ATT,则请求 + if (state == AppLifecycleState.resumed && + !_hasRequestedATT && + Platform.isIOS && + !_isInitializing) { + _requestATTWhenReady(); + } + } + + Future _initializeAds() async { + _isInitializing = true; + + try { + // iOS 平台:智能等待并请求 ATT 权限 + if (Platform.isIOS) { + await _requestATTWhenReady(); + } + + // ATT 请求完成后,初始化广告管理器 + _adManager.initialize(); + + // 等待初始化完成 + await _waitForInitialization(); + + if (mounted) { + // 并行加载三个广告,任意一个成功就立即跳转并显示 + _adManager.loadInitialSplashAd( + onAdReady: (adType) { + if (mounted) { + // 延迟一小段时间后跳转并显示广告 + Future.delayed(const Duration(milliseconds: 500)).then((_) { + if (mounted) { + _navigateToHomeAndShowAd(adType); + } + }); + } + }, + onAllAdsFailed: () { + if (mounted) { + // 所有广告都失败,延迟后进入主页 + Future.delayed(const Duration(seconds: 1)).then((_) { + if (mounted) { + _navigateToHome(); + } + }); + } + }, + ); + } + } catch (e) { + if (mounted) { + // 即使初始化失败,也允许进入主页(广告可能稍后可用) + await Future.delayed(const Duration(seconds: 2)); + if (mounted) { + _navigateToHome(); + } + } + } finally { + _isInitializing = false; + } + } + + /// 导航到主页并显示广告 + void _navigateToHomeAndShowAd(InterstitialAdType adType) { + // 对于开屏场景,直接跳到 HomePage,并把已准备好的广告类型传过去 + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => MainScreen(initialSplashAdType: adType), + ), + ); + } + + /// 导航到主页 + void _navigateToHome() { + if (widget.homePageBuilder != null) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => widget.homePageBuilder!()), + ); + } + } + + /// 智能等待并请求 ATT 权限,确保没有其他权限对话框在显示 + Future _requestATTWhenReady() async { + if (_hasRequestedATT || _isWaitingForATT) return; + + _isWaitingForATT = true; + + try { + // 检查当前ATT状态 + final currentStatus = + await ATTrackingManager.getTrackingAuthorizationStatus(); + debugPrint('LoadingPage: ATT Status: $currentStatus'); + + if (currentStatus != ATTStatus.NotDetermined) { + _hasRequestedATT = true; + _isWaitingForATT = false; + return; + } + + // 初始延迟,让系统处理其他权限 + await Future.delayed(const Duration(milliseconds: 2000)); + + // 智能等待:检测是否有其他系统对话框 + await _waitForSystemDialogsToClear(); + + // 最终检查:确保状态仍然是 NotDetermined + final finalStatus = + await ATTrackingManager.getTrackingAuthorizationStatus(); + if (finalStatus == ATTStatus.NotDetermined && mounted) { + _hasRequestedATT = true; + debugPrint('LoadingPage: Requesting ATT permission...'); + final returnedStatus = + await ATTrackingManager.requestTrackingAuthorization(); + debugPrint('LoadingPage: ATT Status returned: $returnedStatus'); + } else { + _hasRequestedATT = true; + } + } catch (e) { + debugPrint('LoadingPage: ATT request error: $e'); + _hasRequestedATT = true; + } finally { + _isWaitingForATT = false; + } + } + + /// 等待系统对话框清除 + /// 通过监听应用生命周期状态变化来检测是否有系统对话框 + Future _waitForSystemDialogsToClear() async { + const maxWaitTime = Duration(seconds: 30); // 最多等待30秒 + const checkInterval = Duration(milliseconds: 500); + final startTime = DateTime.now(); + + AppLifecycleState? lastState = WidgetsBinding.instance.lifecycleState; + int stableCount = 0; + const requiredStableCount = 4; // 需要连续2秒保持稳定状态 + + while (DateTime.now().difference(startTime) < maxWaitTime) { + if (!mounted) break; + + await Future.delayed(checkInterval); + + final currentState = WidgetsBinding.instance.lifecycleState; + + // 如果应用状态稳定在 resumed,说明没有系统对话框 + if (currentState == AppLifecycleState.resumed) { + if (lastState == AppLifecycleState.resumed) { + stableCount++; + if (stableCount >= requiredStableCount) { + debugPrint('LoadingPage: App state stable, ready for ATT request'); + break; + } + } else { + stableCount = 1; + } + } else { + // 如果状态不是 resumed,重置计数 + stableCount = 0; + debugPrint('LoadingPage: App state: $currentState, waiting...'); + } + + lastState = currentState; + } + + // 额外的安全延迟 + await Future.delayed(const Duration(milliseconds: 1000)); + } + + /// 等待广告管理器初始化完成 + Future _waitForInitialization() async { + // 轮询检查初始化状态,最多等待 15 秒 + const maxWaitTime = Duration(seconds: 15); + const checkInterval = Duration(milliseconds: 200); + final startTime = DateTime.now(); + + while (DateTime.now().difference(startTime) < maxWaitTime) { + if (_adManager.isInitialized) { + return; + } + await Future.delayed(checkInterval); + } + + // 如果超时仍未初始化,记录警告但继续 + if (!_adManager.isInitialized) { + debugPrint('Warning: Ad initialization timeout, proceeding anyway'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xff141420), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo with animation + TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: const Duration(milliseconds: 800), + curve: Curves.easeOut, + builder: (context, value, child) { + return Opacity( + opacity: value, + child: Transform.scale(scale: value, child: child), + ); + }, + child: Image.asset( + 'assets/images/stonicons.png', + width: 100, + height: 100, + fit: BoxFit.contain, + ), + ), + const SizedBox(height: 5), + // 状态文本 + const Text( + 'Resource Loading', + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 5), + // Loading 动画 + const SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator( + strokeWidth: 4, + valueColor: AlwaysStoppedAnimation(Color(0xFFFFFFFF)), + ), + ), + const SizedBox(height: 30), + + ], + ), + ), + ); + } +} diff --git a/lib/widgets/weather_details_sheet.dart b/lib/widgets/weather_details_sheet.dart new file mode 100644 index 0000000..25645ae --- /dev/null +++ b/lib/widgets/weather_details_sheet.dart @@ -0,0 +1,351 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import '../constants.dart'; +import '../models/weather_models.dart'; + +class WeatherDetailsSheet extends StatefulWidget { + final Forecast forecast; + final CurrentWeather current; + + const WeatherDetailsSheet({ + super.key, + required this.forecast, + required this.current, + }); + + @override + State createState() => _WeatherDetailsSheetState(); +} + +class _WeatherDetailsSheetState extends State { + late DraggableScrollableController _sheetController; + + @override + void initState() { + super.initState(); + _sheetController = DraggableScrollableController(); + } + + @override + void dispose() { + _sheetController.dispose(); + super.dispose(); + } + + Future _animateSheetUp() async { + if (!_sheetController.isAttached) return; + + final currentSize = _sheetController.size; + double targetSize; + + // Determine target size based on current position + if (currentSize < 0.4) { + // If small, expand to medium (0.6) + targetSize = 0.6; + } else if (currentSize < 0.75) { + // If medium, expand to large (0.9) + targetSize = 0.9; + } else { + // If large, collapse to small (0.2) + targetSize = 0.2; + } + + await _sheetController.animateTo( + targetSize, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + ); + } + + @override + Widget build(BuildContext context) { + return DraggableScrollableSheet( + controller: _sheetController, + initialChildSize: 0.2, + minChildSize: 0.15, + maxChildSize: 0.9, + builder: (BuildContext context, ScrollController scrollController) { + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 15.0, sigmaY: 15.0), + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.4), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Clickable header area + GestureDetector( + onTap: _animateSheetUp, + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Center( + child: Container( + width: 40, + height: 5, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.4), + borderRadius: BorderRadius.circular(12), + ), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.keyboard_arrow_up, + color: Colors.white.withOpacity(0.7), + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Tap to expand', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + ), + ), + // Scrollable content + Expanded( + child: ListView( + controller: scrollController, + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + children: [ + const SizedBox(height: 8), + _buildSectionTitle('Hourly Forecast'), + SizedBox( + height: 140, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: + widget.forecast.forecastday[0].hour.length, + itemBuilder: (context, index) { + final hour = + widget.forecast.forecastday[0].hour[index]; + if (DateTime.parse( + hour.time, + ).isBefore(DateTime.now())) { + return const SizedBox.shrink(); + } + return _HourlyForecastItem(hour: hour); + }, + ), + ), + const Divider(color: Colors.white24, height: 32), + _buildSectionTitle('7-Day Forecast'), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.forecast.forecastday.length, + itemBuilder: (context, index) { + final day = widget.forecast.forecastday[index]; + return _DailyForecastItem(day: day); + }, + ), + const Divider(color: Colors.white24, height: 32), + _buildSectionTitle('Current Details'), + _buildDetailsGrid(), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildSectionTitle(String title) { + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Text( + title.toUpperCase(), + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 14, + fontWeight: FontWeight.bold, + letterSpacing: 1.1, + ), + ), + ); + } + + Widget _buildDetailsGrid() { + final astro = widget.forecast.forecastday[0].astro; + return GridView.count( + crossAxisCount: 2, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + childAspectRatio: 2.1, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + children: [ + _DetailItem( + title: 'Feels Like', + value: '${widget.current.feelslikeC.round()}°', + ), + _DetailItem(title: 'UV Index', value: '${widget.current.uv.round()}'), + _DetailItem(title: 'Humidity', value: '${widget.current.humidity}%'), + _DetailItem( + title: 'Wind', + value: '${widget.current.windKph.round()} km/h', + ), + _DetailItem(title: 'Sunrise', value: astro.sunrise), + _DetailItem(title: 'Sunset', value: astro.sunset), + ], + ); + } +} + +class _HourlyForecastItem extends StatelessWidget { + final Hour hour; + const _HourlyForecastItem({required this.hour}); + + @override + Widget build(BuildContext context) { + final time = DateFormat.j().format(DateTime.parse(hour.time)); + return Container( + width: 80, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + time, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + Image.network(hour.condition.icon, width: 40, height: 40), + Text( + '${hour.tempC.round()}°', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } +} + +class _DailyForecastItem extends StatelessWidget { + final ForecastDay day; + const _DailyForecastItem({required this.day}); + + @override + Widget build(BuildContext context) { + final date = DateTime.parse(day.date); + final dayName = DateFormat.E().format(date); + final isToday = + DateFormat.yMd().format(date) == + DateFormat.yMd().format(DateTime.now()); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isToday ? 'Today' : dayName, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + Image.network(day.day.condition.icon, width: 30, height: 30), + Text( + '${day.day.dailyChanceOfRain}%', + style: TextStyle( + color: Colors.blue.shade200, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + Text( + 'H: ${day.day.maxtempC.round()}° L: ${day.day.mintempC.round()}°', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } +} + +class _DetailItem extends StatelessWidget { + final String title; + final String value; + const _DetailItem({required this.title, required this.value}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: kCardBackground, + borderRadius: kCardBorderRadius, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title.toUpperCase(), + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + value, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/weather_info_overlay.dart b/lib/widgets/weather_info_overlay.dart new file mode 100644 index 0000000..891676c --- /dev/null +++ b/lib/widgets/weather_info_overlay.dart @@ -0,0 +1,310 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../providers.dart'; +import '../models/weather_models.dart'; +import '../providers/city_provider.dart'; +import '../screens/city_management_screen.dart'; + +class WeatherInfoOverlay extends ConsumerWidget { + final WeatherModel weather; + const WeatherInfoOverlay({super.key, required this.weather}); + + void _showCitySelector(BuildContext context, WidgetRef ref) { + final cities = ref.read(cityListProvider); + final currentCityAsync = ref.read(currentCityProvider); + + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (context) => Container( + decoration: BoxDecoration( + color: const Color(0xFF1C1C2E), + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: const EdgeInsets.only(top: 12, bottom: 8), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.3), + borderRadius: BorderRadius.circular(2), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + const Text( + 'Switch City', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.add, color: Colors.white), + onPressed: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CityManagementScreen(), + ), + ); + }, + tooltip: 'Add city', + ), + ], + ), + ), + Flexible( + child: ListView.builder( + shrinkWrap: true, + itemCount: cities.length, + itemBuilder: (context, index) { + final city = cities[index]; + return currentCityAsync.when( + data: (currentQuery) => ListTile( + leading: Icon( + city.query == currentQuery + ? Icons.location_on + : Icons.location_city, + color: city.query == currentQuery + ? Colors.blueAccent + : Colors.white70, + ), + title: Text( + city.name, + style: TextStyle( + color: Colors.white, + fontWeight: city.query == currentQuery + ? FontWeight.bold + : FontWeight.normal, + ), + ), + subtitle: Text( + city.displayName, + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 12, + ), + ), + trailing: city.query == currentQuery + ? const Icon(Icons.check, color: Colors.blueAccent) + : null, + onTap: () { + ref + .read(weatherProvider.notifier) + .switchCity(city.query); + Navigator.pop(context); + }, + ), + loading: () => ListTile( + leading: const Icon( + Icons.location_city, + color: Colors.white70, + ), + title: Text( + city.name, + style: const TextStyle(color: Colors.white), + ), + subtitle: Text( + city.displayName, + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 12, + ), + ), + onTap: () { + ref + .read(weatherProvider.notifier) + .switchCity(city.query); + Navigator.pop(context); + }, + ), + error: (_, __) => ListTile( + leading: const Icon( + Icons.location_city, + color: Colors.white70, + ), + title: Text( + city.name, + style: const TextStyle(color: Colors.white), + ), + subtitle: Text( + city.displayName, + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 12, + ), + ), + onTap: () { + ref + .read(weatherProvider.notifier) + .switchCity(city.query); + Navigator.pop(context); + }, + ), + ); + }, + ), + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final current = weather.current; + final location = weather.location; + final forecastDay = weather.forecast.forecastday[0]; + final cities = ref.watch(cityListProvider); + + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 20.0), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => _showCitySelector(context, ref), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + const Icon( + Icons.location_on, + color: Colors.white, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + location.name, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + '${cities.length} ${cities.length == 1 ? 'city' : 'cities'}', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 12, + ), + ), + ], + ), + ), + const Icon( + Icons.arrow_drop_down, + color: Colors.white, + ), + ], + ), + ), + ), + ), + const SizedBox(width: 8), + IconButton( + icon: const Icon(Icons.location_city, color: Colors.white), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CityManagementScreen(), + ), + ); + }, + tooltip: 'Manage cities', + ), + ], + ), + const Spacer(flex: 2), + Text( + location.name, + style: const TextStyle( + color: Colors.white, + fontSize: 32, + fontWeight: FontWeight.bold, + shadows: [Shadow(blurRadius: 4, color: Colors.black38)], + ), + ), + Text( + '${location.region}, ${location.country}', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 16, + shadows: const [Shadow(blurRadius: 2, color: Colors.black38)], + ), + ), + const SizedBox(height: 16), + Text( + '${current.tempC.round()}°', + style: const TextStyle( + color: Colors.white, + fontSize: 96, + fontWeight: FontWeight.w200, + shadows: [Shadow(blurRadius: 6, color: Colors.black38)], + ), + ), + Text( + current.condition.text, + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 20, + fontWeight: FontWeight.w500, + shadows: const [Shadow(blurRadius: 2, color: Colors.black38)], + ), + ), + Text( + 'H: ${forecastDay.day.maxtempC.round()}° L: ${forecastDay.day.mintempC.round()}°', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 18, + fontWeight: FontWeight.w500, + shadows: const [Shadow(blurRadius: 2, color: Colors.black38)], + ), + ), + const Spacer(flex: 3), + const Icon( + Icons.keyboard_arrow_up, + color: Colors.white70, + size: 24, + ), + Text( + 'Swipe up for details', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 14, + ), + ), + const SizedBox(height: 60), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..7fbfb19 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1009 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.flutter-io.cn" + source: hosted + version: "85.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.65" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.7.1" + app_tracking_transparency: + dependency: "direct main" + description: + name: app_tracking_transparency + sha256: "1f71f4d8402552fbf8b191d4edab301f233c1af794878b7bc56c708470ffd74c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.6+1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.5" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5+1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.8" + dart_earcut: + dependency: transitive + description: + name: dart_earcut + sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: "8ca4832c7a6d145ce987fd07d6dfbb8c91d9058178342f20de6305fb77b1b40d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.1.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: d00234716f415f89eb5c2cefb1238d7fd2f3120275d71414b84ae434dcdb7a19 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.5" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: e42b294e51aedb4bd4b761a886c8d6b473c44b44aa4c0b47cab06b2c66ac3fba + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.1+1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.3.1" + firebase_crashlytics: + dependency: "direct main" + description: + name: firebase_crashlytics + sha256: "8d52022ee6fdd224e92c042f297d1fd0ec277195c49f39fa61b8cc500a639f00" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.6" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: "97c6a97b35e3d3dafe38fb053a65086a1efb125022d292161405848527cc25a4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.8.16" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.2" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "13.0.4" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.6.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.13" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.6" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.3" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.5" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.3" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.2" + kk_device_infos: + dependency: "direct main" + description: + path: "/Volumes/samsungssd/fluterPlus/kk_device_infos" + relative: false + source: path + version: "0.0.1" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + lists: + dependency: transitive + description: + name: lists + sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.16.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.9.1" + polylabel: + dependency: transitive + description: + name: polylabel + sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.2" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.0.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.18" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.26.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.6" + test_core: + dependency: transitive + description: + name: test_core + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + unicode: + dependency: transitive + description: + name: unicode + sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" + unity_levelplay_mediation: + dependency: "direct main" + description: + name: unity_levelplay_mediation + sha256: da0b872dc7ed2fb80135b591ae7f8cd92ad2b613a55654db977955fc3c676cf7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.0.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.5.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.flutter-io.cn" + source: hosted + version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + water_drop_nav_bar: + dependency: "direct main" + description: + name: water_drop_nav_bar + sha256: "65e71c836445f44d63acbc83b1d81f0df74166f95d53c02f9faeb41cc4a2dcb6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.15.0" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.9.2 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..56ff9cf --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,119 @@ +name: atmo_sphere +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.1.0+1 + +environment: + sdk: ^3.9.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + kk_device_infos: + path: /Volumes/samsungssd/fluterPlus/kk_device_infos + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + flutter_riverpod: ^3.0.3 + http: ^1.6.0 + intl: ^0.20.2 + share_plus: ^12.0.1 + water_drop_nav_bar: ^2.2.2 + shared_preferences: ^2.3.3 + flutter_map: ^7.0.2 + latlong2: ^0.9.1 + geolocator: ^13.0.1 + firebase_core: ^4.3.0 + firebase_crashlytics: ^5.0.6 + firebase_analytics: ^12.1.0 + + app_tracking_transparency: ^2.0.6+1 + unity_levelplay_mediation: ^9.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/images/ + - assets/wallpapers/clear_day/ + - assets/wallpapers/clear_night/ + - assets/wallpapers/cloudy_day/ + - assets/wallpapers/cloudy_night/ + - assets/wallpapers/rain_day/ + - assets/wallpapers/rain_night/ + - assets/wallpapers/snow_day/ + - assets/wallpapers/snow_night/ + - assets/wallpapers/fog_day/ + - assets/wallpapers/fog_night/ + - assets/wallpapers/default/ + + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..644a04f --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:atmo_sphere/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}