app名称:LineInkGuide 包名:com.lineInkGuide.lineInkGuide 1.0未上传

This commit is contained in:
fengshengxiong 2026-01-22 16:37:21 +08:00
commit 6870d64d5f
59 changed files with 5053 additions and 0 deletions

45
.gitignore vendored Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,50 @@
LineInkGuide - 纸感手绘物理解谜
在一本翻开的“物理笔记本”上,用指尖化身墨水钢笔,画出斜坡、挡板和跳台,引导小球穿过星星、安稳落入篮筐。
真实重力与碰撞模拟,让每一次弹跳、滑落、翻转都充满未知;墨水却是有限的——越省墨、线路越巧妙,评分就越高。
【游戏简介】
LineInkGuide 是一款纸感手绘风的物理解谜游戏。
你不用复杂操作,只需画线和擦除:
- 规划小球的运动路线
- 借助关卡中的几何障碍完成二次弹跳、反弹、绕路
- 在有限墨水内,收集所有星星并到达终点篮筐
这不是速度取胜的游戏,而是“更聪明的路线设计”带来的成就感。
【核心玩法】
- 手指画线:在方格纸上任意画线,生成可交互的物理地形
- 真实物理:重力、摩擦、弹性等效果一应俱全,小球行为可预判但充满巧思空间
- 收集星星:每一关都有分布巧妙的星星,需要绕路、反弹或多段落差来取得
- 墨水限制:每条线都会消耗墨水,保留更多墨水通关可解锁更高星级
- 一键重开:线路不满意随时重来,尝试各种不同解法
【关卡设计】
- 超过 30 个精心打磨的关卡,从入门教程到高难度挑战循序渐进
- 前期:坡道与基础弹跳,帮助理解物理规则和画线手感
- 中期:迷宫式地形、圆形/三角形障碍,需要多段路线配合
- 后期:窄缝穿行、精准落点与多层平台,对空间想象和操作节奏有更高要求
- 每关都有多种解法,支持你“只要能到篮筐,就是好路线”的创意。
【成就与收集】
- 通关可获得 15 星评价,高星通关能解锁更多关卡与奖励
- 内置成就系统:首次通关、完美节墨、限时通关等多种挑战目标
- 收集星星可兑换小球皮肤,解锁不同颜色与表情,为你的“主角小球”换装
【皮肤与外观】
- 多套可解锁小球皮肤:可爱、简洁、炫彩等不同风格
- 皮肤仅改变外观,不影响数值,完全公平
- 所有皮肤均可通过游戏内获得的星星解锁,无需强制付费
【操作与体验】
- 单指操作,轻松上手,适合短时间游玩或长时间“钻研一关”
- 清爽手账风 UI 与方格纸背景,让画线、碰撞和弹跳更直观
- 细腻触感反馈与简洁音效,专注于解谜本身不过度打扰
【适合人群】
- 喜欢物理解谜、画线类游戏的玩家
- 想在通勤、睡前、排队时打发碎片时间,又不想被复杂操作打断的人
- 喜欢收集成就、追求完美通关和多解法挑战的玩家
LineInkGuide 让你在一页页“纸上世界”中,用最简单的几笔,画出最巧妙的解法。
现在就打开笔记本,开始你的物理解谜之旅吧。

28
analysis_options.yaml Normal file
View 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
View 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

View 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>

View File

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

43
ios/Podfile Normal file
View 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
View 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

View 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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View 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)
}
}

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View 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.

View 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>

View 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
View 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>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View 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
View 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
View 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
View 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
View 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
// 13: slope-style levels
// 46: bounce-focused layouts
// 79: multi-layer maze platforms
// 1012: 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
View 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
View 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 (13).',
rewardStars: 2,
),
AchievementDef(
id: 'bounce_master',
title: 'Bounce Master',
description: 'Clear a bounce level (46).',
rewardStars: 2,
),
AchievementDef(
id: 'maze_master',
title: 'Maze Navigator',
description: 'Clear a multi-layer maze level (79).',
rewardStars: 2,
),
AchievementDef(
id: 'gap_master',
title: 'Gap Sniper',
description: 'Clear a narrow-gap level (1012).',
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
View 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
View 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
View 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

Binary file not shown.

562
pubspec.lock Normal file
View 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
View 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
View 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);
});
}