app名称:LineInkGuide 包名:com.lineInkGuide.lineInkGuide 1.0未上传
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# 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
|
||||||
30
.metadata
Normal file
@ -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'
|
||||||
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# line_ink_guide
|
||||||
|
|
||||||
|
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.
|
||||||
50
about.txt
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
LineInkGuide - 纸感手绘物理解谜
|
||||||
|
|
||||||
|
在一本翻开的“物理笔记本”上,用指尖化身墨水钢笔,画出斜坡、挡板和跳台,引导小球穿过星星、安稳落入篮筐。
|
||||||
|
真实重力与碰撞模拟,让每一次弹跳、滑落、翻转都充满未知;墨水却是有限的——越省墨、线路越巧妙,评分就越高。
|
||||||
|
|
||||||
|
【游戏简介】
|
||||||
|
LineInkGuide 是一款纸感手绘风的物理解谜游戏。
|
||||||
|
你不用复杂操作,只需画线和擦除:
|
||||||
|
- 规划小球的运动路线
|
||||||
|
- 借助关卡中的几何障碍完成二次弹跳、反弹、绕路
|
||||||
|
- 在有限墨水内,收集所有星星并到达终点篮筐
|
||||||
|
这不是速度取胜的游戏,而是“更聪明的路线设计”带来的成就感。
|
||||||
|
|
||||||
|
【核心玩法】
|
||||||
|
- 手指画线:在方格纸上任意画线,生成可交互的物理地形
|
||||||
|
- 真实物理:重力、摩擦、弹性等效果一应俱全,小球行为可预判但充满巧思空间
|
||||||
|
- 收集星星:每一关都有分布巧妙的星星,需要绕路、反弹或多段落差来取得
|
||||||
|
- 墨水限制:每条线都会消耗墨水,保留更多墨水通关可解锁更高星级
|
||||||
|
- 一键重开:线路不满意随时重来,尝试各种不同解法
|
||||||
|
|
||||||
|
【关卡设计】
|
||||||
|
- 超过 30 个精心打磨的关卡,从入门教程到高难度挑战循序渐进
|
||||||
|
- 前期:坡道与基础弹跳,帮助理解物理规则和画线手感
|
||||||
|
- 中期:迷宫式地形、圆形/三角形障碍,需要多段路线配合
|
||||||
|
- 后期:窄缝穿行、精准落点与多层平台,对空间想象和操作节奏有更高要求
|
||||||
|
- 每关都有多种解法,支持你“只要能到篮筐,就是好路线”的创意。
|
||||||
|
|
||||||
|
【成就与收集】
|
||||||
|
- 通关可获得 1–5 星评价,高星通关能解锁更多关卡与奖励
|
||||||
|
- 内置成就系统:首次通关、完美节墨、限时通关等多种挑战目标
|
||||||
|
- 收集星星可兑换小球皮肤,解锁不同颜色与表情,为你的“主角小球”换装
|
||||||
|
|
||||||
|
【皮肤与外观】
|
||||||
|
- 多套可解锁小球皮肤:可爱、简洁、炫彩等不同风格
|
||||||
|
- 皮肤仅改变外观,不影响数值,完全公平
|
||||||
|
- 所有皮肤均可通过游戏内获得的星星解锁,无需强制付费
|
||||||
|
|
||||||
|
【操作与体验】
|
||||||
|
- 单指操作,轻松上手,适合短时间游玩或长时间“钻研一关”
|
||||||
|
- 清爽手账风 UI 与方格纸背景,让画线、碰撞和弹跳更直观
|
||||||
|
- 细腻触感反馈与简洁音效,专注于解谜本身不过度打扰
|
||||||
|
|
||||||
|
【适合人群】
|
||||||
|
- 喜欢物理解谜、画线类游戏的玩家
|
||||||
|
- 想在通勤、睡前、排队时打发碎片时间,又不想被复杂操作打断的人
|
||||||
|
- 喜欢收集成就、追求完美通关和多解法挑战的玩家
|
||||||
|
|
||||||
|
LineInkGuide 让你在一页页“纸上世界”中,用最简单的几笔,画出最巧妙的解法。
|
||||||
|
现在就打开笔记本,开始你的物理解谜之旅吧。
|
||||||
|
|
||||||
28
analysis_options.yaml
Normal file
@ -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
|
||||||
34
ios/.gitignore
vendored
Normal file
@ -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
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>13.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
2
ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
2
ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
43
ios/Podfile
Normal file
@ -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!
|
||||||
|
|
||||||
|
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
|
||||||
42
ios/Podfile.lock
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
PODS:
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
- 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`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
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"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 53a6aebc29ccee84c41f92f409fc20cd4ca011f1
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
760
ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,760 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
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 */; };
|
||||||
|
8D84F9555F6F40D0EEE7F485 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6534200F80A89663344CB4EB /* Pods_Runner.framework */; };
|
||||||
|
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 */; };
|
||||||
|
BB0D059E5301BC33A89EB8BD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDED1D4077DA5B40EEAF4BF5 /* 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 = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
2D729D9009EC2C1B1CE230E2 /* 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 = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
|
594D3EFE1EA3C2E9F3E02AF1 /* 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 = "<group>"; };
|
||||||
|
6534200F80A89663344CB4EB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
6F54257010725B9B55951209 /* 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 = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
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 = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
986470D097C3A4C910EC32B0 /* 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 = "<group>"; };
|
||||||
|
98895563C0252F9C09CD9ADB /* 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 = "<group>"; };
|
||||||
|
B0AEE5D753F3E01FC4D50660 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
DDED1D4077DA5B40EEAF4BF5 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
5BD8D3E9577EF42020AFA2A8 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
BB0D059E5301BC33A89EB8BD /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
8D84F9555F6F40D0EEE7F485 /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
0882DA02201E54CD28CD85D0 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
6534200F80A89663344CB4EB /* Pods_Runner.framework */,
|
||||||
|
DDED1D4077DA5B40EEAF4BF5 /* Pods_RunnerTests.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
DCB1550627752C335F5DEF87 /* Pods */,
|
||||||
|
0882DA02201E54CD28CD85D0 /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
DCB1550627752C335F5DEF87 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
6F54257010725B9B55951209 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
98895563C0252F9C09CD9ADB /* Pods-Runner.release.xcconfig */,
|
||||||
|
B0AEE5D753F3E01FC4D50660 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
986470D097C3A4C910EC32B0 /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
594D3EFE1EA3C2E9F3E02AF1 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
2D729D9009EC2C1B1CE230E2 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
C1B9F219FC4C30508A533792 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
5BD8D3E9577EF42020AFA2A8 /* 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 = (
|
||||||
|
EE3AF85E7E8ACFBF579289D7 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
5588F88B52EB83EEC6C6661C /* [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 */,
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
5588F88B52EB83EEC6C6661C /* [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";
|
||||||
|
};
|
||||||
|
C1B9F219FC4C30508A533792 /* [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;
|
||||||
|
};
|
||||||
|
EE3AF85E7E8ACFBF579289D7 /* [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;
|
||||||
|
};
|
||||||
|
/* 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 = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* 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_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 7223AXXDS7;
|
||||||
|
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.quickcoloring.quickcoloring;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = devvzl;
|
||||||
|
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 = 986470D097C3A4C910EC32B0 /* 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.lineInkGuide.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 = 594D3EFE1EA3C2E9F3E02AF1 /* 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.lineInkGuide.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 = 2D729D9009EC2C1B1CE230E2 /* 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.lineInkGuide.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_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 7223AXXDS7;
|
||||||
|
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.quickcoloring.quickcoloring;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = devvzl;
|
||||||
|
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_STYLE = Manual;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 7223AXXDS7;
|
||||||
|
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.quickcoloring.quickcoloring;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = devvzl;
|
||||||
|
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 */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
101
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
10
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
ios/Runner/AppDelegate.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -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.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
47
ios/Runner/Info.plist
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>LineInkGuide</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>line_ink_guide</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@ -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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
94
lib/app_theme.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
|
||||||
|
// --- Global Theme ---
|
||||||
|
class AppTheme {
|
||||||
|
static const Color paperBg = Color(0xFFFAF9F6);
|
||||||
|
static const Color gridLine = Color(0xFFE5E4E2);
|
||||||
|
static const Color inkPrimary = Color(0xFF2D3436);
|
||||||
|
static const Color accentRed = Color(0xFFE17055);
|
||||||
|
static const Color accentYellow = Color(0xFFF1C40F);
|
||||||
|
static const Color accentGreen = Color(0xFF27AE60);
|
||||||
|
static const Color obstacleColor = Color(0xFF636E72);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Skin Themes ---
|
||||||
|
class SkinThemeData {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final int cost; // 星星价格
|
||||||
|
final Color ballColor;
|
||||||
|
final Color eyeColor;
|
||||||
|
final Color inkColor;
|
||||||
|
|
||||||
|
const SkinThemeData({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.cost,
|
||||||
|
required this.ballColor,
|
||||||
|
required this.eyeColor,
|
||||||
|
required this.inkColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SkinManager {
|
||||||
|
static const List<SkinThemeData> skins = [
|
||||||
|
SkinThemeData(
|
||||||
|
id: 'classic',
|
||||||
|
name: 'Classic Paper',
|
||||||
|
cost: 0,
|
||||||
|
ballColor: AppTheme.accentRed,
|
||||||
|
eyeColor: Colors.white,
|
||||||
|
inkColor: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
SkinThemeData(
|
||||||
|
id: 'ocean',
|
||||||
|
name: 'Ocean Blue',
|
||||||
|
cost: 20,
|
||||||
|
ballColor: Color(0xFF0984E3),
|
||||||
|
eyeColor: Color(0xFFE3F2FD),
|
||||||
|
inkColor: Color(0xFF0D47A1),
|
||||||
|
),
|
||||||
|
SkinThemeData(
|
||||||
|
id: 'forest',
|
||||||
|
name: 'Forest Green',
|
||||||
|
cost: 20,
|
||||||
|
ballColor: Color(0xFF00B894),
|
||||||
|
eyeColor: Color(0xFFE8F5E9),
|
||||||
|
inkColor: Color(0xFF1B5E20),
|
||||||
|
),
|
||||||
|
SkinThemeData(
|
||||||
|
id: 'sunset',
|
||||||
|
name: 'Sunset Orange',
|
||||||
|
cost: 25,
|
||||||
|
ballColor: Color(0xFFFF7675),
|
||||||
|
eyeColor: Color(0xFFFFF3E0),
|
||||||
|
inkColor: Color(0xFFE65100),
|
||||||
|
),
|
||||||
|
SkinThemeData(
|
||||||
|
id: 'neon',
|
||||||
|
name: 'Neon Night',
|
||||||
|
cost: 30,
|
||||||
|
ballColor: Color(0xFF6C5CE7),
|
||||||
|
eyeColor: Color(0xFFF3E5F5),
|
||||||
|
inkColor: Color(0xFF00E5FF),
|
||||||
|
),
|
||||||
|
SkinThemeData(
|
||||||
|
id: 'shadow',
|
||||||
|
name: 'Shadow Black',
|
||||||
|
cost: 35,
|
||||||
|
ballColor: Color(0xFF2D3436),
|
||||||
|
eyeColor: Color(0xFFB0BEC5),
|
||||||
|
inkColor: Color(0xFF000000),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
static SkinThemeData get current {
|
||||||
|
final id = LevelManager.instance.selectedSkinId;
|
||||||
|
return skins.firstWhere((s) => s.id == id, orElse: () => skins.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkinThemeData byId(String id) {
|
||||||
|
return skins.firstWhere((s) => s.id == id, orElse: () => skins.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
395
lib/game_components.dart
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'app_theme.dart';
|
||||||
|
import 'levels.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
import 'game_engine.dart';
|
||||||
|
|
||||||
|
// --- Game Components ---
|
||||||
|
class Ball extends BodyComponent<LineInkGuideGame> with ContactCallbacks {
|
||||||
|
final Vector2 start;
|
||||||
|
Ball(this.start) : super(priority: 10);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = CircleShape()..radius = 0.6;
|
||||||
|
final body =
|
||||||
|
world.createBody(
|
||||||
|
BodyDef(type: BodyType.dynamic, position: start, bullet: true),
|
||||||
|
)..createFixture(
|
||||||
|
FixtureDef(shape, friction: 0.3, restitution: 0.4, density: 1.0),
|
||||||
|
);
|
||||||
|
body.userData = this; // FIXED: Set userData on the body
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
final skin = SkinManager.current;
|
||||||
|
canvas.drawCircle(Offset.zero, 0.6, Paint()..color = skin.ballColor);
|
||||||
|
canvas.drawCircle(
|
||||||
|
const Offset(-0.15, -0.15),
|
||||||
|
0.15,
|
||||||
|
Paint()..color = skin.eyeColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
// 如果小球掉出关卡范围,则通知游戏失败
|
||||||
|
final pos = body.position;
|
||||||
|
if (game.simulating &&
|
||||||
|
(pos.y > 65 || pos.y < -5 || pos.x < -5 || pos.x > 25)) {
|
||||||
|
game.onBallOutOfBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Star extends BodyComponent<LineInkGuideGame> with ContactCallbacks {
|
||||||
|
final Vector2 pos;
|
||||||
|
bool hit = false;
|
||||||
|
Star(this.pos) : super(priority: 5);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final body = world.createBody(BodyDef(type: BodyType.static, position: pos))
|
||||||
|
..createFixture(FixtureDef(CircleShape()..radius = 0.8, isSensor: true));
|
||||||
|
body.userData = this; // FIXED: Set userData on the body
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
if (other is Ball && !hit) {
|
||||||
|
hit = true;
|
||||||
|
game.collected++;
|
||||||
|
game.starCountNotifier.value = game.collected;
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
final paint = Paint()..color = AppTheme.accentYellow;
|
||||||
|
final path = Path();
|
||||||
|
const int points = 5;
|
||||||
|
const double innerRadius = 0.3;
|
||||||
|
const double outerRadius = 0.7;
|
||||||
|
const double step = math.pi / points;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2 * points; i++) {
|
||||||
|
double radius = (i % 2 == 0) ? outerRadius : innerRadius;
|
||||||
|
double angle = i * step - math.pi / 2;
|
||||||
|
double x = math.cos(angle) * radius;
|
||||||
|
double y = math.sin(angle) * radius;
|
||||||
|
if (i == 0)
|
||||||
|
path.moveTo(x, y);
|
||||||
|
else
|
||||||
|
path.lineTo(x, y);
|
||||||
|
}
|
||||||
|
path.close();
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Goal extends BodyComponent<LineInkGuideGame> with ContactCallbacks {
|
||||||
|
final Vector2 pos;
|
||||||
|
Goal(this.pos) : super(priority: 4);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final body = world.createBody(
|
||||||
|
BodyDef(type: BodyType.static, position: pos),
|
||||||
|
);
|
||||||
|
// 创建物理碰撞体,让小球可以停在上面
|
||||||
|
body.createFixture(
|
||||||
|
FixtureDef(
|
||||||
|
PolygonShape()..setAsBoxXY(2.4, 0.6),
|
||||||
|
friction: 0.6,
|
||||||
|
restitution: 0.3,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// 创建一个sensor用于检测小球接触(用于通关判断)
|
||||||
|
body.createFixture(
|
||||||
|
FixtureDef(PolygonShape()..setAsBoxXY(2.4, 0.6), isSensor: true),
|
||||||
|
);
|
||||||
|
body.userData = this; // FIXED: Set userData on the body
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
if (other is! Ball) return;
|
||||||
|
|
||||||
|
if (game.collected >= game.config.stars.length &&
|
||||||
|
game.winNotifier.value == null) {
|
||||||
|
final r = game.inkNotifier.value / game.config.ink;
|
||||||
|
final s = (r > 0.9)
|
||||||
|
? 5
|
||||||
|
: (r > 0.7)
|
||||||
|
? 4
|
||||||
|
: (r > 0.5)
|
||||||
|
? 3
|
||||||
|
: (r > 0.25)
|
||||||
|
? 2
|
||||||
|
: 1;
|
||||||
|
final clearTime = game.currentRunDuration;
|
||||||
|
final manager = LevelManager.instance;
|
||||||
|
manager.saveProgress(game.config.id, s, levels.length);
|
||||||
|
final unlocked = manager.unlockAchievementsForWin(
|
||||||
|
levelId: game.config.id,
|
||||||
|
stars: s,
|
||||||
|
clearTime: clearTime,
|
||||||
|
);
|
||||||
|
if (unlocked.isNotEmpty) {
|
||||||
|
game.notifyAchievement(unlocked.first);
|
||||||
|
}
|
||||||
|
game.simulating = false;
|
||||||
|
game.winNotifier.value = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
bool ready = game.starCountNotifier.value >= game.config.stars.length;
|
||||||
|
|
||||||
|
// 绘制一个更美观的篮子样式
|
||||||
|
final baseColor = ready ? AppTheme.accentGreen : AppTheme.gridLine;
|
||||||
|
final highlightColor = ready
|
||||||
|
? Color.lerp(AppTheme.accentGreen, Colors.white, 0.3)!
|
||||||
|
: AppTheme.gridLine.withOpacity(0.5);
|
||||||
|
|
||||||
|
// 篮子主体(带圆角的矩形)
|
||||||
|
final basketRect = Rect.fromCenter(
|
||||||
|
center: Offset.zero,
|
||||||
|
width: 4.8,
|
||||||
|
height: 1.2,
|
||||||
|
);
|
||||||
|
final basketPaint = Paint()
|
||||||
|
..color = baseColor
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
canvas.drawRRect(
|
||||||
|
RRect.fromRectAndRadius(basketRect, const Radius.circular(0.3)),
|
||||||
|
basketPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 篮子边框
|
||||||
|
final borderPaint = Paint()
|
||||||
|
..color = baseColor.withOpacity(0.8)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 0.15;
|
||||||
|
canvas.drawRRect(
|
||||||
|
RRect.fromRectAndRadius(basketRect, const Radius.circular(0.3)),
|
||||||
|
borderPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 篮子内部线条(模拟篮子纹理)
|
||||||
|
final innerPaint = Paint()
|
||||||
|
..color = highlightColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 0.08;
|
||||||
|
canvas.drawLine(
|
||||||
|
const Offset(-2.1, -0.3),
|
||||||
|
const Offset(2.1, -0.3),
|
||||||
|
innerPaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(const Offset(-2.1, 0), const Offset(2.1, 0), innerPaint);
|
||||||
|
canvas.drawLine(
|
||||||
|
const Offset(-2.1, 0.3),
|
||||||
|
const Offset(2.1, 0.3),
|
||||||
|
innerPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果未准备好,添加一个锁的图标
|
||||||
|
if (!ready) {
|
||||||
|
final lockPaint = Paint()
|
||||||
|
..color = AppTheme.gridLine
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 0.12;
|
||||||
|
// 简单的锁图标
|
||||||
|
canvas.drawRect(
|
||||||
|
Rect.fromCenter(center: const Offset(0, 0.1), width: 0.4, height: 0.3),
|
||||||
|
lockPaint,
|
||||||
|
);
|
||||||
|
canvas.drawCircle(const Offset(0, -0.05), 0.15, lockPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserLine extends BodyComponent {
|
||||||
|
final List<Vector2> pts;
|
||||||
|
UserLine(this.pts) : super(priority: 8);
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final body = world.createBody(BodyDef(type: BodyType.static));
|
||||||
|
for (int i = 0; i < pts.length - 1; i++) {
|
||||||
|
body.createFixture(
|
||||||
|
FixtureDef(EdgeShape()..set(pts[i], pts[i + 1]), friction: 0.6),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
final skin = SkinManager.current;
|
||||||
|
final p = Paint()
|
||||||
|
..color = skin.inkColor
|
||||||
|
..strokeWidth = 0.25
|
||||||
|
..strokeCap = StrokeCap.round
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
final path = Path()..moveTo(pts.first.x, pts.first.y);
|
||||||
|
for (var pt in pts) path.lineTo(pt.x, pt.y);
|
||||||
|
canvas.drawPath(path, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Block extends BodyComponent {
|
||||||
|
final BlockShape shape;
|
||||||
|
|
||||||
|
Block(this.shape) : super(priority: 3);
|
||||||
|
|
||||||
|
Block.fromRect(Rect r) : shape = BlockShape.fromRect(r), super(priority: 3);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final body = world.createBody(
|
||||||
|
BodyDef(
|
||||||
|
type: BodyType.static,
|
||||||
|
position: shape.position,
|
||||||
|
angle: shape.rotation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (shape.type) {
|
||||||
|
case BlockShapeType.rectangle:
|
||||||
|
final s = PolygonShape()
|
||||||
|
..setAsBox(
|
||||||
|
shape.width / 2,
|
||||||
|
shape.height / 2,
|
||||||
|
Vector2.zero(),
|
||||||
|
0, // 旋转已经在 body 角度中处理
|
||||||
|
);
|
||||||
|
body.createFixture(FixtureDef(s, friction: 0.6, restitution: 0.3));
|
||||||
|
break;
|
||||||
|
case BlockShapeType.triangle:
|
||||||
|
// 创建三角形:顶点向上
|
||||||
|
final vertices = [
|
||||||
|
Vector2(0, -shape.height / 2), // 顶部
|
||||||
|
Vector2(-shape.width / 2, shape.height / 2), // 左下
|
||||||
|
Vector2(shape.width / 2, shape.height / 2), // 右下
|
||||||
|
];
|
||||||
|
final s = PolygonShape()..set(vertices);
|
||||||
|
body.createFixture(FixtureDef(s, friction: 0.6, restitution: 0.3));
|
||||||
|
break;
|
||||||
|
case BlockShapeType.circle:
|
||||||
|
if (shape.radius == null) break;
|
||||||
|
final s = CircleShape()..radius = shape.radius!;
|
||||||
|
body.createFixture(FixtureDef(s, friction: 0.6, restitution: 0.3));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
// BodyComponent 的 render 方法会自动将 canvas 变换到 body 的位置和角度
|
||||||
|
// 所以直接使用 Offset.zero 作为中心点即可
|
||||||
|
// 填充颜色
|
||||||
|
final fillPaint = Paint()
|
||||||
|
..color = AppTheme.obstacleColor
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
// 边框颜色(稍深一点)
|
||||||
|
final borderPaint = Paint()
|
||||||
|
..color = AppTheme.obstacleColor.withOpacity(0.8)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 0.1;
|
||||||
|
|
||||||
|
switch (shape.type) {
|
||||||
|
case BlockShapeType.rectangle:
|
||||||
|
final rect = Rect.fromCenter(
|
||||||
|
center: Offset.zero,
|
||||||
|
width: shape.width,
|
||||||
|
height: shape.height,
|
||||||
|
);
|
||||||
|
canvas.drawRect(rect, fillPaint);
|
||||||
|
canvas.drawRect(rect, borderPaint);
|
||||||
|
break;
|
||||||
|
case BlockShapeType.triangle:
|
||||||
|
final path = Path()
|
||||||
|
..moveTo(0, -shape.height / 2)
|
||||||
|
..lineTo(-shape.width / 2, shape.height / 2)
|
||||||
|
..lineTo(shape.width / 2, shape.height / 2)
|
||||||
|
..close();
|
||||||
|
canvas.drawPath(path, fillPaint);
|
||||||
|
canvas.drawPath(path, borderPaint);
|
||||||
|
break;
|
||||||
|
case BlockShapeType.circle:
|
||||||
|
if (shape.radius == null) break;
|
||||||
|
canvas.drawCircle(Offset.zero, shape.radius!, fillPaint);
|
||||||
|
canvas.drawCircle(Offset.zero, shape.radius!, borderPaint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Boundary extends BodyComponent {
|
||||||
|
final Vector2 a, b;
|
||||||
|
Boundary(this.a, this.b);
|
||||||
|
@override
|
||||||
|
Body createBody() =>
|
||||||
|
world.createBody(BodyDef(type: BodyType.static))
|
||||||
|
..createFixture(FixtureDef(EdgeShape()..set(a, b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
class DrawingPreview extends Component {
|
||||||
|
final List<Vector2> pts;
|
||||||
|
DrawingPreview(this.pts);
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
if (pts.length < 2) return;
|
||||||
|
final skin = SkinManager.current;
|
||||||
|
final p = Paint()
|
||||||
|
..color = skin.inkColor.withOpacity(0.3)
|
||||||
|
..strokeWidth = 0.2
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
final path = Path()..moveTo(pts.first.x, pts.first.y);
|
||||||
|
for (var pt in pts) path.lineTo(pt.x, pt.y);
|
||||||
|
canvas.drawPath(path, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackgroundGrid extends Component {
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
final gridPaint = Paint()
|
||||||
|
..color = AppTheme.gridLine
|
||||||
|
..strokeWidth = 0.05;
|
||||||
|
for (double i = 0; i <= 20; i += 2)
|
||||||
|
canvas.drawLine(Offset(i, 0), Offset(i, 60), gridPaint);
|
||||||
|
for (double i = 0; i <= 60; i += 2)
|
||||||
|
canvas.drawLine(Offset(0, i), Offset(20, i), gridPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridPainter extends CustomPainter {
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size s) {
|
||||||
|
final p = Paint()
|
||||||
|
..color = AppTheme.gridLine
|
||||||
|
..strokeWidth = 1;
|
||||||
|
for (double i = 0; i < s.width; i += 32)
|
||||||
|
canvas.drawLine(Offset(i, 0), Offset(i, s.height), p);
|
||||||
|
for (double i = 0; i < s.height; i += 32)
|
||||||
|
canvas.drawLine(Offset(0, i), Offset(s.width, i), p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter old) => false;
|
||||||
|
}
|
||||||
215
lib/game_engine.dart
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/components.dart' hide Block;
|
||||||
|
import 'package:flame/events.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'app_theme.dart';
|
||||||
|
import 'levels.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
import 'game_components.dart';
|
||||||
|
|
||||||
|
// --- Physics Engine ---
|
||||||
|
class LineInkGuideGame extends Forge2DGame with DragCallbacks {
|
||||||
|
final LevelConfig config;
|
||||||
|
final ValueNotifier<double> inkNotifier = ValueNotifier(0);
|
||||||
|
final ValueNotifier<int> starCountNotifier = ValueNotifier(0);
|
||||||
|
final ValueNotifier<int?> winNotifier = ValueNotifier(null);
|
||||||
|
final ValueNotifier<bool> failNotifier = ValueNotifier(false);
|
||||||
|
final ValueNotifier<AchievementDef?> achievementNotifier = ValueNotifier(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<Vector2> _drawPoints = [];
|
||||||
|
DrawingPreview? _preview;
|
||||||
|
bool _simulating = false;
|
||||||
|
int _collected = 0;
|
||||||
|
DateTime? _runStartTime;
|
||||||
|
|
||||||
|
// 公共访问器,供组件使用
|
||||||
|
int get collected => _collected;
|
||||||
|
set collected(int value) => _collected = value;
|
||||||
|
bool get simulating => _simulating;
|
||||||
|
set simulating(bool value) => _simulating = value;
|
||||||
|
|
||||||
|
LineInkGuideGame({required this.config}) : super(gravity: Vector2(0, 0));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color backgroundColor() => AppTheme.paperBg;
|
||||||
|
|
||||||
|
Ball? _ball;
|
||||||
|
List<Star> _stars = [];
|
||||||
|
Goal? _goal;
|
||||||
|
List<Block> _blocks = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
camera.viewfinder.anchor = Anchor.topLeft;
|
||||||
|
_updateZoom();
|
||||||
|
_initializeLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeLevel() {
|
||||||
|
world.removeAll(world.children);
|
||||||
|
world.add(BackgroundGrid());
|
||||||
|
|
||||||
|
// Add Entities
|
||||||
|
_ball = Ball(config.ballPos);
|
||||||
|
world.add(_ball!);
|
||||||
|
_stars.clear();
|
||||||
|
for (var sPos in config.stars) {
|
||||||
|
final star = Star(sPos);
|
||||||
|
_stars.add(star);
|
||||||
|
world.add(star);
|
||||||
|
}
|
||||||
|
_goal = Goal(config.goalPos);
|
||||||
|
world.add(_goal!);
|
||||||
|
_blocks.clear();
|
||||||
|
for (var blockShape in config.blocks) {
|
||||||
|
final block = Block(blockShape);
|
||||||
|
_blocks.add(block);
|
||||||
|
world.add(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borders
|
||||||
|
world.add(Boundary(Vector2(0, 0), Vector2(20, 0)));
|
||||||
|
world.add(Boundary(Vector2(0, 0), Vector2(0, 60)));
|
||||||
|
world.add(Boundary(Vector2(20, 0), Vector2(20, 60)));
|
||||||
|
|
||||||
|
// 初始化状态
|
||||||
|
_simulating = false;
|
||||||
|
_collected = 0;
|
||||||
|
starCountNotifier.value = 0;
|
||||||
|
inkNotifier.value = config.ink;
|
||||||
|
winNotifier.value = null;
|
||||||
|
failNotifier.value = false;
|
||||||
|
_runStartTime = null;
|
||||||
|
_drawPoints.clear();
|
||||||
|
world.gravity = Vector2(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateZoom() {
|
||||||
|
camera.viewfinder.zoom = size.x / 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onGameResize(Vector2 size) {
|
||||||
|
super.onGameResize(size);
|
||||||
|
_updateZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetLevel() {
|
||||||
|
_simulating = false;
|
||||||
|
_collected = 0;
|
||||||
|
starCountNotifier.value = 0;
|
||||||
|
inkNotifier.value = config.ink;
|
||||||
|
winNotifier.value = null;
|
||||||
|
failNotifier.value = false;
|
||||||
|
_runStartTime = null;
|
||||||
|
_drawPoints.clear();
|
||||||
|
|
||||||
|
// 停止重力
|
||||||
|
world.gravity = Vector2(0, 0);
|
||||||
|
_preview?.removeFromParent();
|
||||||
|
|
||||||
|
// 移除所有用户绘制的线条
|
||||||
|
final linesToRemove = <UserLine>[];
|
||||||
|
for (var child in world.children) {
|
||||||
|
if (child is UserLine) {
|
||||||
|
linesToRemove.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var line in linesToRemove) {
|
||||||
|
line.removeFromParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置小球位置和速度
|
||||||
|
if (_ball != null && _ball!.isMounted) {
|
||||||
|
_ball!.body.setTransform(config.ballPos, 0);
|
||||||
|
_ball!.body.linearVelocity = Vector2.zero();
|
||||||
|
_ball!.body.angularVelocity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除所有现有星星并重新添加
|
||||||
|
final starsToRemove = <Star>[];
|
||||||
|
for (var child in world.children) {
|
||||||
|
if (child is Star) {
|
||||||
|
starsToRemove.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var star in starsToRemove) {
|
||||||
|
star.removeFromParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新添加所有星星
|
||||||
|
_stars.clear();
|
||||||
|
for (var sPos in config.stars) {
|
||||||
|
final star = Star(sPos);
|
||||||
|
_stars.add(star);
|
||||||
|
world.add(star);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startSimulation() {
|
||||||
|
if (_simulating) return;
|
||||||
|
if (winNotifier.value != null || failNotifier.value) return;
|
||||||
|
_simulating = true;
|
||||||
|
world.gravity = Vector2(0, 32);
|
||||||
|
_preview?.removeFromParent();
|
||||||
|
_runStartTime = DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onDragStart(DragStartEvent event) {
|
||||||
|
super.onDragStart(event);
|
||||||
|
if (_simulating || inkNotifier.value <= 0) return;
|
||||||
|
_drawPoints.clear();
|
||||||
|
_drawPoints.add(screenToWorld(event.localPosition));
|
||||||
|
world.add(_preview = DrawingPreview(_drawPoints));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onDragUpdate(DragUpdateEvent event) {
|
||||||
|
super.onDragUpdate(event);
|
||||||
|
if (_simulating || inkNotifier.value <= 0) return;
|
||||||
|
final p = screenToWorld(event.localEndPosition);
|
||||||
|
if (_drawPoints.isEmpty) return;
|
||||||
|
double d = (p - _drawPoints.last).length;
|
||||||
|
if (d > 0.25 && inkNotifier.value - (d * 10) > 0) {
|
||||||
|
_drawPoints.add(p);
|
||||||
|
inkNotifier.value -= d * 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onDragEnd(DragEndEvent event) {
|
||||||
|
super.onDragEnd(event);
|
||||||
|
_preview?.removeFromParent();
|
||||||
|
if (_drawPoints.length > 1) world.add(UserLine(List.from(_drawPoints)));
|
||||||
|
_drawPoints.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isSimulating => simulating;
|
||||||
|
|
||||||
|
Duration? get currentRunDuration =>
|
||||||
|
_runStartTime == null ? null : DateTime.now().difference(_runStartTime!);
|
||||||
|
|
||||||
|
void onBallOutOfBounds() {
|
||||||
|
if (!_simulating || winNotifier.value != null || failNotifier.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_simulating = false;
|
||||||
|
failNotifier.value = true;
|
||||||
|
_runStartTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyAchievement(AchievementDef def) {
|
||||||
|
achievementNotifier.value = def;
|
||||||
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
|
if (achievementNotifier.value == def) {
|
||||||
|
achievementNotifier.value = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
607
lib/levels.dart
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
// --- Level Data Models ---
|
||||||
|
enum BlockShapeType { rectangle, triangle, circle }
|
||||||
|
|
||||||
|
class BlockShape {
|
||||||
|
final BlockShapeType type;
|
||||||
|
final Vector2 position;
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
final double? radius; // 用于圆形
|
||||||
|
final double rotation; // 旋转角度(弧度)
|
||||||
|
|
||||||
|
BlockShape.rectangle(
|
||||||
|
this.position,
|
||||||
|
this.width,
|
||||||
|
this.height, {
|
||||||
|
this.rotation = 0,
|
||||||
|
}) : type = BlockShapeType.rectangle,
|
||||||
|
radius = null;
|
||||||
|
|
||||||
|
BlockShape.triangle(
|
||||||
|
this.position,
|
||||||
|
this.width,
|
||||||
|
this.height, {
|
||||||
|
this.rotation = 0,
|
||||||
|
}) : type = BlockShapeType.triangle,
|
||||||
|
radius = null;
|
||||||
|
|
||||||
|
BlockShape.circle(this.position, double radiusValue)
|
||||||
|
: type = BlockShapeType.circle,
|
||||||
|
radius = radiusValue,
|
||||||
|
width = radiusValue * 2,
|
||||||
|
height = radiusValue * 2,
|
||||||
|
rotation = 0;
|
||||||
|
|
||||||
|
// 兼容旧的 Rect 格式
|
||||||
|
BlockShape.fromRect(Rect rect)
|
||||||
|
: type = BlockShapeType.rectangle,
|
||||||
|
position = Vector2(rect.center.dx, rect.center.dy),
|
||||||
|
width = rect.width,
|
||||||
|
height = rect.height,
|
||||||
|
radius = null,
|
||||||
|
rotation = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LevelConfig {
|
||||||
|
final int id;
|
||||||
|
final Vector2 ballPos;
|
||||||
|
final List<Vector2> stars;
|
||||||
|
final Vector2 goalPos;
|
||||||
|
final double ink;
|
||||||
|
final List<BlockShape> blocks;
|
||||||
|
|
||||||
|
LevelConfig({
|
||||||
|
required this.id,
|
||||||
|
required this.ballPos,
|
||||||
|
required this.stars,
|
||||||
|
required this.goalPos,
|
||||||
|
required this.ink,
|
||||||
|
this.blocks = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 兼容旧的 Rect 列表
|
||||||
|
LevelConfig.fromRects({
|
||||||
|
required this.id,
|
||||||
|
required this.ballPos,
|
||||||
|
required this.stars,
|
||||||
|
required this.goalPos,
|
||||||
|
required this.ink,
|
||||||
|
List<Rect> blocks = const [],
|
||||||
|
}) : blocks = blocks.map((r) => BlockShape.fromRect(r)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Level Definitions ---
|
||||||
|
// Hand-crafted themed levels
|
||||||
|
// 1–3: slope-style levels
|
||||||
|
// 4–6: bounce-focused layouts
|
||||||
|
// 7–9: multi-layer maze platforms
|
||||||
|
// 10–12: narrow-gap precision levels
|
||||||
|
final List<LevelConfig> levels = [
|
||||||
|
// 1-6: 基础阶段
|
||||||
|
LevelConfig(
|
||||||
|
id: 1,
|
||||||
|
ballPos: Vector2(3, 5),
|
||||||
|
stars: [Vector2(9, 18)],
|
||||||
|
goalPos: Vector2(10, 38),
|
||||||
|
ink: 650,
|
||||||
|
blocks: [],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 2,
|
||||||
|
ballPos: Vector2(4, 10),
|
||||||
|
stars: [Vector2(9, 18), Vector2(13, 24)],
|
||||||
|
goalPos: Vector2(16, 38),
|
||||||
|
ink: 700,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(4.5, 22), 5, 1.2),
|
||||||
|
BlockShape.triangle(Vector2(11.5, 26), 5, 1.2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 3,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(5, 18), Vector2(11, 24), Vector2(15, 30)],
|
||||||
|
goalPos: Vector2(17, 39),
|
||||||
|
ink: 750,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(3, 20), 4, 1.2),
|
||||||
|
BlockShape.circle(Vector2(11, 26), 1),
|
||||||
|
BlockShape.triangle(Vector2(10, 32), 6, 1.2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 4,
|
||||||
|
ballPos: Vector2(4, 10),
|
||||||
|
stars: [Vector2(6, 17), Vector2(10, 22)],
|
||||||
|
goalPos: Vector2(14, 37),
|
||||||
|
ink: 750,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(8, 19), 12, 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(8, 26), 12, 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 5,
|
||||||
|
ballPos: Vector2(5, 8),
|
||||||
|
stars: [Vector2(6, 16), Vector2(6, 24), Vector2(6, 30)],
|
||||||
|
goalPos: Vector2(14, 37),
|
||||||
|
ink: 800,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(4, 20), 4, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(4, 28), 4, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(13, 30), 6, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 6,
|
||||||
|
ballPos: Vector2(4, 8),
|
||||||
|
stars: [Vector2(6, 18), Vector2(14, 20)],
|
||||||
|
goalPos: Vector2(10, 38),
|
||||||
|
ink: 820,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(3.25, 24), 1.25),
|
||||||
|
BlockShape.circle(Vector2(16.75, 24), 1.25),
|
||||||
|
BlockShape.rectangle(Vector2(9, 28), 10, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// 7-12: 进阶阶段
|
||||||
|
LevelConfig(
|
||||||
|
id: 7,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(8, 15), Vector2(12, 24)],
|
||||||
|
goalPos: Vector2(16, 37),
|
||||||
|
ink: 850,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(5.5, 17), 7, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(11.5, 22), 7, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(5.5, 28), 7, 0.8),
|
||||||
|
BlockShape.circle(Vector2(15, 21), 0.75),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 8,
|
||||||
|
ballPos: Vector2(4, 10),
|
||||||
|
stars: [Vector2(6, 20), Vector2(12, 22), Vector2(16, 26)],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 900,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(8, 18), 12, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(8, 24), 12, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(11.5, 30), 7, 0.8),
|
||||||
|
BlockShape.circle(Vector2(3.25, 31), 1.25),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 9,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(6, 17), Vector2(14, 20)],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 900,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(5, 19), 6, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(13, 22), 6, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(8, 28), 12, 0.8),
|
||||||
|
BlockShape.circle(Vector2(9.4, 21), 0.4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 10,
|
||||||
|
ballPos: Vector2(4, 8),
|
||||||
|
stars: [Vector2(10, 20)],
|
||||||
|
goalPos: Vector2(16, 37),
|
||||||
|
ink: 850,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(7.6, 22), 0.6),
|
||||||
|
BlockShape.circle(Vector2(12.6, 22), 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(8, 28), 12, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 11,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(9, 18), Vector2(9, 26)],
|
||||||
|
goalPos: Vector2(9, 37),
|
||||||
|
ink: 900,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(6.6, 24), 0.6),
|
||||||
|
BlockShape.circle(Vector2(12.6, 24), 0.6),
|
||||||
|
BlockShape.triangle(Vector2(3.5, 24), 3, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(16.5, 24), 3, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 12,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(7.5, 20), Vector2(10, 28)],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 950,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(9, 21), 0.5),
|
||||||
|
BlockShape.circle(Vector2(11.5, 21), 0.5),
|
||||||
|
BlockShape.triangle(Vector2(9, 28), 8, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// 13-18: 高级阶段 - 复杂组合与精准跳跃
|
||||||
|
LevelConfig(
|
||||||
|
id: 13,
|
||||||
|
ballPos: Vector2(2, 8),
|
||||||
|
stars: [Vector2(5, 16), Vector2(12, 24), Vector2(16, 32)],
|
||||||
|
goalPos: Vector2(18, 38),
|
||||||
|
ink: 1000,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(6, 19), 8, 0.6),
|
||||||
|
BlockShape.circle(Vector2(13, 22), 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(8, 30), 10, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(16, 35), 6, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 14,
|
||||||
|
ballPos: Vector2(5, 8),
|
||||||
|
stars: [Vector2(8, 18), Vector2(8, 26), Vector2(8, 34)],
|
||||||
|
goalPos: Vector2(14, 39),
|
||||||
|
ink: 1050,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(3, 20), 1),
|
||||||
|
BlockShape.triangle(Vector2(13, 22), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(3, 30), 1),
|
||||||
|
BlockShape.rectangle(Vector2(10, 35), 8, 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 15,
|
||||||
|
ballPos: Vector2(2, 10),
|
||||||
|
stars: [Vector2(6, 18), Vector2(14, 22), Vector2(10, 30)],
|
||||||
|
goalPos: Vector2(16, 38),
|
||||||
|
ink: 1100,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(4, 19), 5, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(12, 21), 10, 0.6),
|
||||||
|
BlockShape.circle(Vector2(9, 26), 0.9),
|
||||||
|
BlockShape.triangle(Vector2(14, 31), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(5, 34), 0.7),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 16,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(10, 17), Vector2(16, 24)],
|
||||||
|
goalPos: Vector2(10, 38),
|
||||||
|
ink: 1050,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(6, 18), 0.8),
|
||||||
|
BlockShape.circle(Vector2(14, 18), 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(10, 23), 14, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(6, 30), 5, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(14, 30), 5, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 17,
|
||||||
|
ballPos: Vector2(4, 8),
|
||||||
|
stars: [Vector2(7, 20), Vector2(13, 26), Vector2(10, 34)],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1150,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(5, 19), 6, 0.6),
|
||||||
|
BlockShape.circle(Vector2(10, 22), 0.7),
|
||||||
|
BlockShape.rectangle(Vector2(15, 25), 6, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(8, 31), 7, 0.8),
|
||||||
|
BlockShape.circle(Vector2(12, 31), 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 18,
|
||||||
|
ballPos: Vector2(2, 10),
|
||||||
|
stars: [Vector2(4, 18), Vector2(16, 24), Vector2(10, 32)],
|
||||||
|
goalPos: Vector2(15, 39),
|
||||||
|
ink: 1200,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(3, 17), 4, 0.8),
|
||||||
|
BlockShape.circle(Vector2(10, 20), 0.9),
|
||||||
|
BlockShape.triangle(Vector2(17, 23), 5, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(8, 29), 12, 0.6),
|
||||||
|
BlockShape.circle(Vector2(6, 35), 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// 19-24: 专家阶段 - 密集障碍与多重跳跃
|
||||||
|
LevelConfig(
|
||||||
|
id: 19,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [Vector2(6, 17), Vector2(14, 20), Vector2(9, 28), Vector2(16, 34)],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1250,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(5, 18), 0.7),
|
||||||
|
BlockShape.circle(Vector2(13, 18), 0.7),
|
||||||
|
BlockShape.rectangle(Vector2(9, 23), 12, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(4, 29), 5, 0.8),
|
||||||
|
BlockShape.circle(Vector2(14, 29), 0.9),
|
||||||
|
BlockShape.rectangle(Vector2(9, 35), 10, 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 20,
|
||||||
|
ballPos: Vector2(5, 8),
|
||||||
|
stars: [Vector2(8, 16), Vector2(8, 24), Vector2(8, 32)],
|
||||||
|
goalPos: Vector2(8, 39),
|
||||||
|
ink: 1300,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(4, 19), 0.6),
|
||||||
|
BlockShape.circle(Vector2(12, 19), 0.6),
|
||||||
|
BlockShape.triangle(Vector2(8, 22), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(4, 27), 0.6),
|
||||||
|
BlockShape.circle(Vector2(12, 27), 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(8, 31), 8, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(8, 36), 4, 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 21,
|
||||||
|
ballPos: Vector2(2, 10),
|
||||||
|
stars: [Vector2(5, 18), Vector2(15, 22), Vector2(6, 30), Vector2(14, 36)],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1350,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(4, 17), 6, 0.6),
|
||||||
|
BlockShape.circle(Vector2(10, 19), 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(16, 21), 6, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(3, 26), 5, 0.8),
|
||||||
|
BlockShape.circle(Vector2(10, 28), 0.9),
|
||||||
|
BlockShape.triangle(Vector2(17, 32), 5, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(10, 35), 12, 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 22,
|
||||||
|
ballPos: Vector2(4, 8),
|
||||||
|
stars: [Vector2(7, 18), Vector2(13, 20), Vector2(10, 28), Vector2(7, 34)],
|
||||||
|
goalPos: Vector2(16, 38),
|
||||||
|
ink: 1400,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(5, 16), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(15, 18), 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(9, 23), 14, 0.6),
|
||||||
|
BlockShape.circle(Vector2(5, 28), 0.7),
|
||||||
|
BlockShape.circle(Vector2(15, 28), 0.7),
|
||||||
|
BlockShape.triangle(Vector2(10, 32), 7, 0.8),
|
||||||
|
BlockShape.circle(Vector2(16, 35), 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 23,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [
|
||||||
|
Vector2(4, 17),
|
||||||
|
Vector2(16, 19),
|
||||||
|
Vector2(8, 26),
|
||||||
|
Vector2(12, 32),
|
||||||
|
Vector2(6, 36),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(14, 39),
|
||||||
|
ink: 1450,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(3, 18), 0.7),
|
||||||
|
BlockShape.rectangle(Vector2(10, 20), 12, 0.6),
|
||||||
|
BlockShape.circle(Vector2(17, 18), 0.7),
|
||||||
|
BlockShape.triangle(Vector2(6, 25), 5, 0.8),
|
||||||
|
BlockShape.circle(Vector2(12, 28), 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(9, 31), 10, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(4, 34), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(14, 36), 0.9),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 24,
|
||||||
|
ballPos: Vector2(2, 10),
|
||||||
|
stars: [
|
||||||
|
Vector2(6, 17),
|
||||||
|
Vector2(14, 20),
|
||||||
|
Vector2(4, 26),
|
||||||
|
Vector2(16, 30),
|
||||||
|
Vector2(10, 36),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1500,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(3, 18), 8, 0.6),
|
||||||
|
BlockShape.circle(Vector2(14, 19), 0.8),
|
||||||
|
BlockShape.triangle(Vector2(9, 23), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(4, 27), 0.7),
|
||||||
|
BlockShape.circle(Vector2(16, 27), 0.7),
|
||||||
|
BlockShape.rectangle(Vector2(10, 30), 12, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(7, 34), 5, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(13, 34), 5, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// 25-30: 大师阶段 - 极限挑战
|
||||||
|
LevelConfig(
|
||||||
|
id: 25,
|
||||||
|
ballPos: Vector2(3, 8),
|
||||||
|
stars: [
|
||||||
|
Vector2(5, 16),
|
||||||
|
Vector2(15, 19),
|
||||||
|
Vector2(8, 24),
|
||||||
|
Vector2(12, 29),
|
||||||
|
Vector2(6, 34),
|
||||||
|
Vector2(16, 37),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1550,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(4, 17), 0.6),
|
||||||
|
BlockShape.circle(Vector2(16, 17), 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(9, 21), 12, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(5, 26), 5, 0.8),
|
||||||
|
BlockShape.circle(Vector2(13, 27), 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(8, 31), 10, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(14, 31), 4, 0.8),
|
||||||
|
BlockShape.circle(Vector2(4, 35), 0.7),
|
||||||
|
BlockShape.circle(Vector2(16, 35), 0.7),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 26,
|
||||||
|
ballPos: Vector2(5, 8),
|
||||||
|
stars: [
|
||||||
|
Vector2(8, 15),
|
||||||
|
Vector2(8, 21),
|
||||||
|
Vector2(8, 27),
|
||||||
|
Vector2(8, 33),
|
||||||
|
Vector2(8, 37),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(8, 39),
|
||||||
|
ink: 1600,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(4, 18), 0.65),
|
||||||
|
BlockShape.circle(Vector2(12, 18), 0.65),
|
||||||
|
BlockShape.triangle(Vector2(8, 22), 7, 0.8),
|
||||||
|
BlockShape.circle(Vector2(3, 25), 0.7),
|
||||||
|
BlockShape.circle(Vector2(13, 25), 0.7),
|
||||||
|
BlockShape.rectangle(Vector2(8, 29), 10, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(5, 31), 4, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(11, 31), 4, 0.8),
|
||||||
|
BlockShape.circle(Vector2(8, 35), 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 27,
|
||||||
|
ballPos: Vector2(2, 10),
|
||||||
|
stars: [
|
||||||
|
Vector2(4, 16),
|
||||||
|
Vector2(16, 18),
|
||||||
|
Vector2(6, 24),
|
||||||
|
Vector2(14, 26),
|
||||||
|
Vector2(8, 32),
|
||||||
|
Vector2(12, 35),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1650,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.rectangle(Vector2(3, 17), 8, 0.6),
|
||||||
|
BlockShape.circle(Vector2(16, 19), 0.75),
|
||||||
|
BlockShape.triangle(Vector2(6, 22), 6, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(14, 25), 8, 0.6),
|
||||||
|
BlockShape.circle(Vector2(5, 28), 0.8),
|
||||||
|
BlockShape.circle(Vector2(15, 28), 0.8),
|
||||||
|
BlockShape.triangle(Vector2(10, 30), 8, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(8, 34), 12, 0.6),
|
||||||
|
BlockShape.circle(Vector2(3, 36), 0.8),
|
||||||
|
BlockShape.circle(Vector2(17, 36), 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 28,
|
||||||
|
ballPos: Vector2(4, 8),
|
||||||
|
stars: [
|
||||||
|
Vector2(6, 16),
|
||||||
|
Vector2(14, 18),
|
||||||
|
Vector2(4, 24),
|
||||||
|
Vector2(16, 26),
|
||||||
|
Vector2(10, 31),
|
||||||
|
Vector2(8, 36),
|
||||||
|
Vector2(12, 37),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1700,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(5, 17), 0.6),
|
||||||
|
BlockShape.circle(Vector2(15, 17), 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(9, 20), 12, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(3, 25), 5, 0.8),
|
||||||
|
BlockShape.triangle(Vector2(17, 25), 5, 0.8),
|
||||||
|
BlockShape.circle(Vector2(10, 27), 0.9),
|
||||||
|
BlockShape.rectangle(Vector2(6, 30), 8, 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(14, 30), 8, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(10, 33), 6, 0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 29,
|
||||||
|
ballPos: Vector2(3, 10),
|
||||||
|
stars: [
|
||||||
|
Vector2(5, 15),
|
||||||
|
Vector2(15, 17),
|
||||||
|
Vector2(8, 21),
|
||||||
|
Vector2(12, 23),
|
||||||
|
Vector2(4, 28),
|
||||||
|
Vector2(16, 31),
|
||||||
|
Vector2(10, 35),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1750,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.triangle(Vector2(4, 16), 5, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(14, 18), 8, 0.6),
|
||||||
|
BlockShape.circle(Vector2(8, 20), 0.75),
|
||||||
|
BlockShape.circle(Vector2(12, 20), 0.75),
|
||||||
|
BlockShape.triangle(Vector2(3, 26), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(16, 29), 0.85),
|
||||||
|
BlockShape.rectangle(Vector2(8, 32), 12, 0.6),
|
||||||
|
BlockShape.circle(Vector2(5, 34), 0.75),
|
||||||
|
BlockShape.circle(Vector2(15, 34), 0.75),
|
||||||
|
BlockShape.triangle(Vector2(10, 36), 5, 0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
LevelConfig(
|
||||||
|
id: 30,
|
||||||
|
ballPos: Vector2(2, 8),
|
||||||
|
stars: [
|
||||||
|
Vector2(4, 15),
|
||||||
|
Vector2(16, 16),
|
||||||
|
Vector2(7, 20),
|
||||||
|
Vector2(13, 22),
|
||||||
|
Vector2(6, 27),
|
||||||
|
Vector2(14, 29),
|
||||||
|
Vector2(9, 34),
|
||||||
|
Vector2(11, 35),
|
||||||
|
],
|
||||||
|
goalPos: Vector2(10, 39),
|
||||||
|
ink: 1800,
|
||||||
|
blocks: [
|
||||||
|
BlockShape.circle(Vector2(3, 17), 0.65),
|
||||||
|
BlockShape.circle(Vector2(17, 17), 0.65),
|
||||||
|
BlockShape.rectangle(Vector2(9, 19), 14, 0.6),
|
||||||
|
BlockShape.triangle(Vector2(5, 23), 6, 0.8),
|
||||||
|
BlockShape.circle(Vector2(13, 24), 0.8),
|
||||||
|
BlockShape.circle(Vector2(4, 28), 0.7),
|
||||||
|
BlockShape.circle(Vector2(16, 28), 0.7),
|
||||||
|
BlockShape.triangle(Vector2(10, 30), 8, 0.8),
|
||||||
|
BlockShape.rectangle(Vector2(7, 33), 6, 0.6),
|
||||||
|
BlockShape.rectangle(Vector2(13, 33), 6, 0.6),
|
||||||
|
BlockShape.circle(Vector2(10, 36), 0.9),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
15
lib/main.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
import 'ui_screens.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await LevelManager.instance.load();
|
||||||
|
runApp(
|
||||||
|
const MaterialApp(
|
||||||
|
title: 'LineInkGuide',
|
||||||
|
home: MainMenuScreen(),
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
246
lib/managers.dart
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'app_theme.dart';
|
||||||
|
|
||||||
|
// --- Achievement System ---
|
||||||
|
class AchievementDef {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final int rewardStars;
|
||||||
|
|
||||||
|
const AchievementDef({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.rewardStars,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Level Progress Manager ---
|
||||||
|
class LevelManager {
|
||||||
|
static final LevelManager instance = LevelManager._internal();
|
||||||
|
LevelManager._internal();
|
||||||
|
|
||||||
|
final Map<int, int> _bestStars = {};
|
||||||
|
int highestUnlockedLevel = 1;
|
||||||
|
int totalStars = 0; // 当前可用星星(用于兑换)
|
||||||
|
String selectedSkinId = 'classic';
|
||||||
|
final Set<String> ownedSkins = {'classic'};
|
||||||
|
final Map<String, bool> _unlockedAchievements = {};
|
||||||
|
|
||||||
|
static const _bestStarsKey = 'bestStars';
|
||||||
|
static const _highestLevelKey = 'highestUnlockedLevel';
|
||||||
|
static const _totalStarsKey = 'totalStars';
|
||||||
|
static const _ownedSkinsKey = 'ownedSkins';
|
||||||
|
static const _selectedSkinKey = 'selectedSkin';
|
||||||
|
static const _achievementsKey = 'achievements';
|
||||||
|
|
||||||
|
static const List<AchievementDef> achievementsCatalog = [
|
||||||
|
AchievementDef(
|
||||||
|
id: 'first_clear',
|
||||||
|
title: 'First Steps',
|
||||||
|
description: 'Clear any level for the first time.',
|
||||||
|
rewardStars: 3,
|
||||||
|
),
|
||||||
|
AchievementDef(
|
||||||
|
id: 'perfect_run',
|
||||||
|
title: 'Perfect Ink',
|
||||||
|
description: 'Earn 5 stars in a single level.',
|
||||||
|
rewardStars: 5,
|
||||||
|
),
|
||||||
|
AchievementDef(
|
||||||
|
id: 'speed_runner',
|
||||||
|
title: 'Speed Runner',
|
||||||
|
description: 'Clear a level within 30 seconds.',
|
||||||
|
rewardStars: 5,
|
||||||
|
),
|
||||||
|
AchievementDef(
|
||||||
|
id: 'slope_master',
|
||||||
|
title: 'Slope Master',
|
||||||
|
description: 'Clear a slope level (1–3).',
|
||||||
|
rewardStars: 2,
|
||||||
|
),
|
||||||
|
AchievementDef(
|
||||||
|
id: 'bounce_master',
|
||||||
|
title: 'Bounce Master',
|
||||||
|
description: 'Clear a bounce level (4–6).',
|
||||||
|
rewardStars: 2,
|
||||||
|
),
|
||||||
|
AchievementDef(
|
||||||
|
id: 'maze_master',
|
||||||
|
title: 'Maze Navigator',
|
||||||
|
description: 'Clear a multi-layer maze level (7–9).',
|
||||||
|
rewardStars: 2,
|
||||||
|
),
|
||||||
|
AchievementDef(
|
||||||
|
id: 'gap_master',
|
||||||
|
title: 'Gap Sniper',
|
||||||
|
description: 'Clear a narrow-gap level (10–12).',
|
||||||
|
rewardStars: 2,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Future<void> load() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final bestStr = prefs.getString(_bestStarsKey);
|
||||||
|
if (bestStr != null) {
|
||||||
|
final Map<String, dynamic> decoded = jsonDecode(bestStr);
|
||||||
|
_bestStars
|
||||||
|
..clear()
|
||||||
|
..addEntries(
|
||||||
|
decoded.entries.map(
|
||||||
|
(e) => MapEntry(int.parse(e.key), e.value as int),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
highestUnlockedLevel = prefs.getInt(_highestLevelKey) ?? 1;
|
||||||
|
if (highestUnlockedLevel < 1) highestUnlockedLevel = 1;
|
||||||
|
|
||||||
|
totalStars = prefs.getInt(_totalStarsKey) ?? 0;
|
||||||
|
|
||||||
|
final owned = prefs.getStringList(_ownedSkinsKey);
|
||||||
|
ownedSkins
|
||||||
|
..clear()
|
||||||
|
..addAll(owned?.toSet() ?? {'classic'});
|
||||||
|
|
||||||
|
selectedSkinId = prefs.getString(_selectedSkinKey) ?? 'classic';
|
||||||
|
if (!ownedSkins.contains(selectedSkinId)) {
|
||||||
|
selectedSkinId = 'classic';
|
||||||
|
}
|
||||||
|
|
||||||
|
final unlockedList = prefs.getStringList(_achievementsKey);
|
||||||
|
_unlockedAchievements
|
||||||
|
..clear()
|
||||||
|
..addEntries(
|
||||||
|
(unlockedList ?? const <String>[]).map((id) => MapEntry(id, true)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _persist() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final mapAsStringInt = _bestStars.map(
|
||||||
|
(key, value) => MapEntry(key.toString(), value),
|
||||||
|
);
|
||||||
|
await prefs.setString(_bestStarsKey, jsonEncode(mapAsStringInt));
|
||||||
|
await prefs.setInt(_highestLevelKey, highestUnlockedLevel);
|
||||||
|
await prefs.setInt(_totalStarsKey, totalStars);
|
||||||
|
await prefs.setStringList(_ownedSkinsKey, ownedSkins.toList());
|
||||||
|
await prefs.setString(_selectedSkinKey, selectedSkinId);
|
||||||
|
await prefs.setStringList(
|
||||||
|
_achievementsKey,
|
||||||
|
_unlockedAchievements.entries
|
||||||
|
.where((e) => e.value)
|
||||||
|
.map((e) => e.key)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearAll() async {
|
||||||
|
_bestStars.clear();
|
||||||
|
highestUnlockedLevel = 1;
|
||||||
|
totalStars = 0;
|
||||||
|
ownedSkins
|
||||||
|
..clear()
|
||||||
|
..add('classic');
|
||||||
|
selectedSkinId = 'classic';
|
||||||
|
await _persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveProgress(int levelId, int stars, int maxLevel) {
|
||||||
|
final oldBest = _bestStars[levelId] ?? 0;
|
||||||
|
if (stars > oldBest) {
|
||||||
|
_bestStars[levelId] = stars;
|
||||||
|
// 只把"新提高"的星星计入总数,避免重复刷
|
||||||
|
totalStars += (stars - oldBest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通关解锁下一关(至少 1 星)
|
||||||
|
if (levelId == highestUnlockedLevel && stars > 0) {
|
||||||
|
if (highestUnlockedLevel < maxLevel) {
|
||||||
|
highestUnlockedLevel = highestUnlockedLevel + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步持久化
|
||||||
|
_persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStars(int levelId) => _bestStars[levelId] ?? 0;
|
||||||
|
|
||||||
|
// 皮肤相关
|
||||||
|
bool isSkinOwned(String skinId) => ownedSkins.contains(skinId);
|
||||||
|
|
||||||
|
bool isSkinSelected(String skinId) => selectedSkinId == skinId;
|
||||||
|
|
||||||
|
bool canAfford(int cost) => totalStars >= cost;
|
||||||
|
|
||||||
|
void selectSkin(String skinId) {
|
||||||
|
if (!isSkinOwned(skinId)) return;
|
||||||
|
selectedSkinId = skinId;
|
||||||
|
_persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool buySkin(SkinThemeData skin) {
|
||||||
|
if (isSkinOwned(skin.id)) return false;
|
||||||
|
if (!canAfford(skin.cost)) return false;
|
||||||
|
totalStars -= skin.cost;
|
||||||
|
ownedSkins.add(skin.id);
|
||||||
|
selectedSkinId = skin.id;
|
||||||
|
_persist();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAchievementUnlocked(String id) => _unlockedAchievements[id] == true;
|
||||||
|
|
||||||
|
List<AchievementDef> unlockAchievementsForWin({
|
||||||
|
required int levelId,
|
||||||
|
required int stars,
|
||||||
|
required Duration? clearTime,
|
||||||
|
}) {
|
||||||
|
final newlyUnlocked = <AchievementDef>[];
|
||||||
|
|
||||||
|
void _tryUnlock(String id) {
|
||||||
|
if (isAchievementUnlocked(id)) return;
|
||||||
|
final def = achievementsCatalog.firstWhere(
|
||||||
|
(a) => a.id == id,
|
||||||
|
orElse: () => achievementsCatalog.first,
|
||||||
|
);
|
||||||
|
_unlockedAchievements[id] = true;
|
||||||
|
totalStars += def.rewardStars;
|
||||||
|
newlyUnlocked.add(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首次通关任意关卡
|
||||||
|
if (stars > 0 && _unlockedAchievements.isEmpty) {
|
||||||
|
_tryUnlock('first_clear');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性拿满 5 星
|
||||||
|
if (stars == 5) {
|
||||||
|
_tryUnlock('perfect_run');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 30 秒内通关一关
|
||||||
|
if (clearTime != null && clearTime.inSeconds <= 30) {
|
||||||
|
_tryUnlock('speed_runner');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主题关成就,基于关卡编号区间
|
||||||
|
if (levelId >= 1 && levelId <= 3) {
|
||||||
|
_tryUnlock('slope_master');
|
||||||
|
} else if (levelId >= 4 && levelId <= 6) {
|
||||||
|
_tryUnlock('bounce_master');
|
||||||
|
} else if (levelId >= 7 && levelId <= 9) {
|
||||||
|
_tryUnlock('maze_master');
|
||||||
|
} else if (levelId >= 10 && levelId <= 12) {
|
||||||
|
_tryUnlock('gap_master');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newlyUnlocked.isNotEmpty) {
|
||||||
|
_persist();
|
||||||
|
}
|
||||||
|
return newlyUnlocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
390
lib/settings_screens.dart
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'app_theme.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
import 'game_components.dart';
|
||||||
|
|
||||||
|
class SettingsScreen extends StatelessWidget {
|
||||||
|
const SettingsScreen({super.key});
|
||||||
|
|
||||||
|
static const _privacyUrl = 'https://example.com/privacy'; // TODO: replace
|
||||||
|
|
||||||
|
Future<void> _openPrivacy() async {
|
||||||
|
final uri = Uri.parse(_privacyUrl);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _shareGame() {
|
||||||
|
Share.share(
|
||||||
|
'Check out LineInkGuide! Draw lines and guide the ball to collect stars.',
|
||||||
|
subject: 'LineInkGuide',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showAbout(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
title: const Text('About'),
|
||||||
|
content: const Text(
|
||||||
|
'LineInkGuide\n\n'
|
||||||
|
'A notebook-style physics puzzle.\n'
|
||||||
|
'Draw ink lines, collect stars, and reach the goal basket.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _clearCache(BuildContext context) async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
title: const Text('Reset Progress'),
|
||||||
|
content: const Text(
|
||||||
|
'This will clear all level progress, stars and skins.\n'
|
||||||
|
'Are you sure?',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: const Text('CANCEL'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
child: const Text('RESET'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed == true) {
|
||||||
|
await LevelManager.instance.clearAll();
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Progress cleared.'),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: const IconThemeData(color: AppTheme.inkPrimary),
|
||||||
|
title: const Text(
|
||||||
|
'SETTINGS',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
// subtle notebook grid background
|
||||||
|
Positioned.fill(child: CustomPaint(painter: GridPainter())),
|
||||||
|
SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'TUNE YOUR NOTEBOOK',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 2,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
// Game section
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_SectionHeader(title: 'GAME'),
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.share,
|
||||||
|
label: 'Share Game',
|
||||||
|
onTap: _shareGame,
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.info_outline,
|
||||||
|
label: 'About',
|
||||||
|
onTap: () => _showAbout(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Privacy & support section
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_SectionHeader(title: 'SUPPORT'),
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.privacy_tip_outlined,
|
||||||
|
label: 'Privacy Policy',
|
||||||
|
onTap: _openPrivacy,
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.feedback_outlined,
|
||||||
|
label: 'Feedback',
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => const FeedbackScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Danger zone
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_SectionHeader(title: 'DATA'),
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.delete_sweep_outlined,
|
||||||
|
label: 'Clear Progress & Cache',
|
||||||
|
iconColor: Colors.redAccent,
|
||||||
|
labelColor: Colors.redAccent,
|
||||||
|
onTap: () => _clearCache(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionHeader extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
const _SectionHeader({required this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
color: AppTheme.paperBg,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 2,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsTile extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final Color? iconColor;
|
||||||
|
final Color? labelColor;
|
||||||
|
|
||||||
|
const _SettingsTile({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.onTap,
|
||||||
|
this.iconColor,
|
||||||
|
this.labelColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Container(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: iconColor ?? AppTheme.inkPrimary,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(icon, size: 18, color: iconColor ?? AppTheme.inkPrimary),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: labelColor ?? AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: const Icon(Icons.chevron_right, color: AppTheme.gridLine),
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FeedbackScreen extends StatefulWidget {
|
||||||
|
const FeedbackScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FeedbackScreen> createState() => _FeedbackScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeedbackScreenState extends State<FeedbackScreen> {
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
bool _submitting = false;
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
if (_controller.text.trim().isEmpty || _submitting) return;
|
||||||
|
setState(() {
|
||||||
|
_submitting = true;
|
||||||
|
});
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: const IconThemeData(color: AppTheme.inkPrimary),
|
||||||
|
title: const Text(
|
||||||
|
'FEEDBACK',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Tell us what you think:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _controller,
|
||||||
|
maxLines: null,
|
||||||
|
expands: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText:
|
||||||
|
'Bugs, level ideas, or anything you want to share...',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
height: 44,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.inkPrimary,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
onPressed: _submitting ? null : _submit,
|
||||||
|
child: _submitting
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'SUBMIT',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
207
lib/skin_shop_screen.dart
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'app_theme.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
|
||||||
|
class SkinShopScreen extends StatefulWidget {
|
||||||
|
const SkinShopScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SkinShopScreen> createState() => _SkinShopScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SkinShopScreenState extends State<SkinShopScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final manager = LevelManager.instance;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: const IconThemeData(color: AppTheme.inkPrimary),
|
||||||
|
title: const Text(
|
||||||
|
'SKINS',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'STAR SHOP',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.star, color: AppTheme.accentYellow),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'${manager.totalStars}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: GridView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
childAspectRatio: 0.9,
|
||||||
|
),
|
||||||
|
itemCount: SkinManager.skins.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final skin = SkinManager.skins[index];
|
||||||
|
final owned = manager.isSkinOwned(skin.id);
|
||||||
|
final selected = manager.isSkinSelected(skin.id);
|
||||||
|
final canAfford = manager.canAfford(skin.cost);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: selected
|
||||||
|
? AppTheme.accentGreen
|
||||||
|
: AppTheme.inkPrimary,
|
||||||
|
width: selected ? 3 : 1.5,
|
||||||
|
),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Skin preview: one ball
|
||||||
|
Container(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: skin.ballColor,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: const Alignment(-0.4, -0.4),
|
||||||
|
child: Container(
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: skin.eyeColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
skin.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
if (skin.cost > 0)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.star,
|
||||||
|
size: 16,
|
||||||
|
color: AppTheme.accentYellow,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 2),
|
||||||
|
Text(
|
||||||
|
'${skin.cost}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Text(
|
||||||
|
'DEFAULT SKIN',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppTheme.gridLine,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: 128,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: BorderSide(
|
||||||
|
color: owned
|
||||||
|
? AppTheme.accentGreen
|
||||||
|
: (canAfford
|
||||||
|
? AppTheme.inkPrimary
|
||||||
|
: AppTheme.gridLine),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
if (!owned) {
|
||||||
|
final success = manager.buySkin(skin);
|
||||||
|
if (!success) return;
|
||||||
|
} else {
|
||||||
|
manager.selectSkin(skin.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
owned
|
||||||
|
? (selected ? 'EQUIPPED' : 'EQUIP')
|
||||||
|
: (canAfford ? 'UNLOCK' : 'NOT ENOUGH STARS'),
|
||||||
|
style: TextStyle(
|
||||||
|
color: owned
|
||||||
|
? AppTheme.accentGreen
|
||||||
|
: (canAfford
|
||||||
|
? AppTheme.inkPrimary
|
||||||
|
: AppTheme.gridLine),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
683
lib/ui_screens.dart
Normal file
@ -0,0 +1,683 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'app_theme.dart';
|
||||||
|
import 'levels.dart';
|
||||||
|
import 'managers.dart';
|
||||||
|
import 'game_engine.dart';
|
||||||
|
import 'game_components.dart';
|
||||||
|
import 'skin_shop_screen.dart';
|
||||||
|
import 'settings_screens.dart';
|
||||||
|
|
||||||
|
// --- UI: Main Menu ---
|
||||||
|
class MainMenuScreen extends StatelessWidget {
|
||||||
|
const MainMenuScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: CustomPaint(painter: GridPainter())),
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"LineInkGuide",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 48,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
letterSpacing: -1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"PHYSICS NOTEBOOK",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
letterSpacing: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 80),
|
||||||
|
_buildBtn(
|
||||||
|
context,
|
||||||
|
"START GAME",
|
||||||
|
() => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => const LevelSelectScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildBtn(
|
||||||
|
context,
|
||||||
|
"SKINS",
|
||||||
|
() => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => const SkinShopScreen()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildBtn(
|
||||||
|
context,
|
||||||
|
"SETTINGS",
|
||||||
|
() => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => const SettingsScreen()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildBtn(context, "HOW TO PLAY", () => _showHelp(context)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBtn(BuildContext context, String text, VoidCallback onTap) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 220,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: const BorderSide(color: AppTheme.inkPrimary, width: 2),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
shape: const RoundedRectangleBorder(),
|
||||||
|
),
|
||||||
|
onPressed: onTap,
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showHelp(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
title: const Text("INSTRUCTIONS"),
|
||||||
|
content: const Text(
|
||||||
|
"• Draw lines to guide the ball.\n• Collect ALL stars to unlock the goal.\n• Reach the goal basket to win.\n• Conserve ink for a higher score!",
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text("READY"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- UI: Level Selection ---
|
||||||
|
class LevelSelectScreen extends StatefulWidget {
|
||||||
|
const LevelSelectScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LevelSelectScreen> createState() => _LevelSelectScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LevelSelectScreenState extends State<LevelSelectScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text(
|
||||||
|
"SELECT LEVEL",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: const IconThemeData(color: AppTheme.inkPrimary),
|
||||||
|
),
|
||||||
|
body: GridView.builder(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
mainAxisSpacing: 20,
|
||||||
|
crossAxisSpacing: 20,
|
||||||
|
childAspectRatio: 0.8,
|
||||||
|
),
|
||||||
|
itemCount: levels.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
int starCount = LevelManager.instance.getStars(levels[index].id);
|
||||||
|
bool isUnlocked =
|
||||||
|
levels[index].id <= LevelManager.instance.highestUnlockedLevel;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
if (!isUnlocked) return;
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => GameView(config: levels[index]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isUnlocked ? AppTheme.inkPrimary : AppTheme.gridLine,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
color: isUnlocked ? null : AppTheme.gridLine.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${index + 1}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (isUnlocked)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: List.generate(
|
||||||
|
5,
|
||||||
|
(i) => Icon(
|
||||||
|
Icons.star,
|
||||||
|
size: 12,
|
||||||
|
color: i < starCount
|
||||||
|
? AppTheme.accentYellow
|
||||||
|
: AppTheme.gridLine,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Icon(Icons.lock, size: 18, color: AppTheme.gridLine),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Game Container ---
|
||||||
|
class GameView extends StatefulWidget {
|
||||||
|
final LevelConfig config;
|
||||||
|
const GameView({super.key, required this.config});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GameView> createState() => _GameViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GameViewState extends State<GameView> {
|
||||||
|
late LineInkGuideGame _game;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_game = LineInkGuideGame(config: widget.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
onPopInvoked: (bool didPop) {
|
||||||
|
if (didPop) return;
|
||||||
|
_showExitConfirmDialog(context);
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
GameWidget(game: _game),
|
||||||
|
SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 8,
|
||||||
|
top: 8,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => _showExitConfirmDialog(context),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"INK",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: _game.inkNotifier,
|
||||||
|
builder: (context, double val, _) => Container(
|
||||||
|
width: 120,
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: (val / widget.config.ink).clamp(0, 1),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
valueColor: const AlwaysStoppedAnimation(
|
||||||
|
AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: _game.starCountNotifier,
|
||||||
|
builder: (context, int count, _) => Text(
|
||||||
|
"STARS: $count / ${widget.config.stars.length}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: const BorderSide(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => _showResetConfirmDialog(context),
|
||||||
|
child: const Text(
|
||||||
|
"RESET",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.inkPrimary,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
onPressed: () => _game.startSimulation(),
|
||||||
|
child: const Text(
|
||||||
|
"START",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Win Overlay
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: _game.winNotifier,
|
||||||
|
builder: (context, int? stars, _) {
|
||||||
|
if (stars == null) return const SizedBox.shrink();
|
||||||
|
return Container(
|
||||||
|
color: Colors.black54,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 300,
|
||||||
|
padding: const EdgeInsets.all(40),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.paperBg,
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"LEVEL CLEAR!",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: List.generate(
|
||||||
|
5,
|
||||||
|
(i) => Icon(
|
||||||
|
Icons.star,
|
||||||
|
size: 40,
|
||||||
|
color: i < stars
|
||||||
|
? AppTheme.accentYellow
|
||||||
|
: AppTheme.gridLine,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
"$stars / 5 STARS",
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: const BorderSide(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text(
|
||||||
|
"CONTINUE",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Achievement banner
|
||||||
|
Positioned(
|
||||||
|
top: 40,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: _game.achievementNotifier,
|
||||||
|
builder: (context, AchievementDef? ach, _) {
|
||||||
|
if (ach == null) return const SizedBox.shrink();
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.accentGreen,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.emoji_events,
|
||||||
|
color: AppTheme.accentYellow,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
ach.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'+${ach.rewardStars} stars · ${ach.description}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Fail Overlay
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: _game.failNotifier,
|
||||||
|
builder: (context, bool failed, _) {
|
||||||
|
if (!failed) return const SizedBox.shrink();
|
||||||
|
return Container(
|
||||||
|
color: Colors.black54,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 280,
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.paperBg,
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"TRY AGAIN?",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
"The ball left the page.\nDo you want to replay this level?",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 28),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: const BorderSide(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"QUIT",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.inkPrimary,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_game.resetLevel();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"RETRY",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showResetConfirmDialog(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) => AlertDialog(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
title: const Text(
|
||||||
|
"RESET LEVEL?",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content: const Text(
|
||||||
|
"This will remove all drawn lines and reset the ball position. Your progress will be lost.",
|
||||||
|
style: TextStyle(color: AppTheme.inkPrimary),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
|
child: const Text(
|
||||||
|
"CANCEL",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.inkPrimary,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(dialogContext);
|
||||||
|
_game.resetLevel();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"RESET",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showExitConfirmDialog(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) => AlertDialog(
|
||||||
|
backgroundColor: AppTheme.paperBg,
|
||||||
|
title: const Text(
|
||||||
|
"EXIT LEVEL?",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content: const Text(
|
||||||
|
"Are you sure you want to exit? Your current progress will be lost.",
|
||||||
|
style: TextStyle(color: AppTheme.inkPrimary),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
|
child: const Text(
|
||||||
|
"CANCEL",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.inkPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.inkPrimary,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(dialogContext);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"EXIT",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
lib1.0未新加功能.zip
Normal file
562
pubspec.lock
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
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: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
flame:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flame
|
||||||
|
sha256: "348136b7d928188a8c9a11cd844d9125b02e7afc6c83bb9a52a16aee88ab7e81"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.34.0"
|
||||||
|
flame_forge2d:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flame_forge2d
|
||||||
|
sha256: "0c0bbf2e0a60f0d1348af127d1110bf7b7259318b44d3fa46d60f7ce8315af51"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.19.2+2"
|
||||||
|
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_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"
|
||||||
|
forge2d:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: forge2d
|
||||||
|
sha256: "0f63d177f2e137a5007b879fda4076d0b81de065fcd72056c6fc896c82758bb7"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.2+1"
|
||||||
|
google_fonts:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: google_fonts
|
||||||
|
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.3"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
ordered_set:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ordered_set
|
||||||
|
sha256: d6c1d053a533e84931a388cbf03f1ad21a0543bf06c7a281859d3ffacd8e15f2
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.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"
|
||||||
|
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"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
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"
|
||||||
|
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_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.6"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.28"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.6"
|
||||||
|
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_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.5"
|
||||||
|
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"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "5.15.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.9.2 <4.0.0"
|
||||||
|
flutter: ">=3.35.0"
|
||||||
95
pubspec.yaml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
name: line_ink_guide
|
||||||
|
description: "LineInkGuide."
|
||||||
|
# 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.0.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
|
||||||
|
|
||||||
|
# The following adds the Cupertino Icons font to your application.
|
||||||
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
cupertino_icons: ^1.0.8
|
||||||
|
flame: ^1.34.0
|
||||||
|
share_plus: ^12.0.1
|
||||||
|
flame_forge2d: ^0.19.2+2
|
||||||
|
shared_preferences: ^2.3.2
|
||||||
|
url_launcher: ^6.3.0
|
||||||
|
google_fonts: ^6.3.3
|
||||||
|
|
||||||
|
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:
|
||||||
|
- images/
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
# 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
|
||||||
30
test/widget_test.dart
Normal file
@ -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:line_ink_guide/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);
|
||||||
|
});
|
||||||
|
}
|
||||||