Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a93dbbe30d | ||
|
|
22a535a6fd | ||
|
|
bd3bc5357e | ||
|
|
a23739d717 | ||
|
|
b62164dde5 | ||
|
|
5cd9a88682 | ||
|
|
e1881acc18 | ||
|
|
f4b914a2ad | ||
|
|
34ef156538 | ||
|
|
cfb551328b |
@@ -15,7 +15,7 @@ migration:
|
||||
- platform: root
|
||||
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
- platform: android
|
||||
- platform: ios
|
||||
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,662 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 60;
|
||||
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 */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
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 */; };
|
||||
/* 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; 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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
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 */,
|
||||
);
|
||||
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 */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
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 = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
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 = 2620;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
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";
|
||||
};
|
||||
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";
|
||||
};
|
||||
/* 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 */,
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
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 = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 37Z4F445LJ;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.davmar.ingtrakapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
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.ingtrak.myapp.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;
|
||||
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.ingtrak.myapp.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;
|
||||
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.ingtrak.myapp.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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
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 = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
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 = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 37Z4F445LJ;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.davmar.ingtrakapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 37Z4F445LJ;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.davmar.ingtrakapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
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 */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||
"version" : "5.21.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||
"version" : "2.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<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 = "Release"
|
||||
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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||
"version" : "5.21.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||
"version" : "2.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -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.
|
||||
@@ -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>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</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="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-101" y="12"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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>Myapp</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>myapp</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1,6 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: FlutterSceneDelegate {
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,647 @@
|
||||
// !$*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 */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
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 */; };
|
||||
/* 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; 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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
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 */,
|
||||
);
|
||||
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 */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
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 = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
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" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
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";
|
||||
};
|
||||
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";
|
||||
};
|
||||
/* 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 */,
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift 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 = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
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;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ingtrak.myapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
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.ingtrak.myapp.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;
|
||||
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.ingtrak.myapp.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;
|
||||
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.ingtrak.myapp.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 = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
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 = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
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;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ingtrak.myapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ingtrak.myapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
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 */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,16 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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>Myapp</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>myapp</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1,6 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: FlutterSceneDelegate {
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -423,7 +423,9 @@ class BankController extends ChangeNotifier {
|
||||
|
||||
final manuals = _collectManualIds(incoming);
|
||||
if (incoming.isNotEmpty) {
|
||||
_remoteTransactions = mergeTransactions(_remoteTransactions, incoming);
|
||||
_remoteTransactions = dropSettledPendingDuplicates(
|
||||
mergeTransactions(_remoteTransactions, incoming),
|
||||
);
|
||||
}
|
||||
// Strip any local copies that match a pending delete (defensive).
|
||||
if (_pendingRemoteDeletes.isNotEmpty) {
|
||||
@@ -522,8 +524,10 @@ class BankController extends ChangeNotifier {
|
||||
final apiTxs = results[1] as List<BankTransaction>;
|
||||
// Bank API is ~89 days. Prefer API rows for overlaps, but keep any
|
||||
// cloud/CSV history that the API window does not include.
|
||||
// Pending rows are not kept from cache — they must still be on the feed.
|
||||
final manuals = _collectManualIds();
|
||||
_remoteTransactions = mergeTransactions(apiTxs, _remoteTransactions);
|
||||
_remoteTransactions =
|
||||
mergeApiTransactionsWithCache(apiTxs, _remoteTransactions);
|
||||
if (!_status.linked) {
|
||||
_status = ConnectStatus(
|
||||
status: 'linked',
|
||||
|
||||
@@ -521,10 +521,15 @@ class TxMetaController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Replace labels on every transaction in [txs] (per-id overrides).
|
||||
///
|
||||
/// When [matchPattern] is set (apply-to-matched), also refreshes the
|
||||
/// merchant rule so future transactions matching the pattern inherit the
|
||||
/// full label set (parent + sub-labels).
|
||||
Future<void> setLabelsForMany(
|
||||
List<BankTransaction> txs,
|
||||
List<String> labelIds,
|
||||
) async {
|
||||
List<String> labelIds, {
|
||||
String? matchPattern,
|
||||
}) async {
|
||||
if (txs.isEmpty) return;
|
||||
|
||||
final expanded = expandWithAncestors([
|
||||
@@ -545,6 +550,22 @@ class TxMetaController extends ChangeNotifier {
|
||||
: current.copyWith(labelIds: unique);
|
||||
}
|
||||
_byId = nextById;
|
||||
|
||||
final pattern = (matchPattern ?? '').trim().toLowerCase();
|
||||
if (pattern.isNotEmpty) {
|
||||
final nextRules = Map<String, List<String>>.of(_labelsByMerchant);
|
||||
if (unique.isEmpty) {
|
||||
nextRules.remove(pattern);
|
||||
for (final tx in txs) {
|
||||
nextRules.remove(tx.merchantKey);
|
||||
nextRules.remove(tx.descriptionKey);
|
||||
}
|
||||
} else {
|
||||
nextRules[pattern] = unique;
|
||||
}
|
||||
_labelsByMerchant = nextRules;
|
||||
}
|
||||
|
||||
_schedulePersist();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ import '../providers/preferences_provider.dart';
|
||||
import '../providers/tx_meta_provider.dart';
|
||||
import '../utils/app_theme.dart';
|
||||
import '../utils/connect_bank.dart';
|
||||
import '../utils/deferred_tab_work.dart';
|
||||
import '../utils/helpers.dart';
|
||||
import '../utils/navigation.dart';
|
||||
import '../utils/tx_filter_metrics.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/home_filter_widget_card.dart';
|
||||
import '../widgets/home_widgets_sheet.dart';
|
||||
@@ -22,6 +24,18 @@ import '../widgets/skip_build_when_covered.dart';
|
||||
import 'transaction_detail_screen.dart';
|
||||
import 'transactions_screen.dart';
|
||||
|
||||
class _HomeSnapshot {
|
||||
const _HomeSnapshot({
|
||||
required this.categorized,
|
||||
required this.statsById,
|
||||
required this.budgetById,
|
||||
});
|
||||
|
||||
final int categorized;
|
||||
final Map<String, TxFilterMetrics> statsById;
|
||||
final Map<String, HomeBudgetMetrics> budgetById;
|
||||
}
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@@ -33,16 +47,93 @@ class HomeScreen extends StatefulWidget {
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
var _editingWidgets = false;
|
||||
final _work = DeferredTabWork<_HomeSnapshot>();
|
||||
|
||||
int _homeCacheKey({
|
||||
required TxMetaController meta,
|
||||
required BankController bank,
|
||||
required List<HomeFilterWidget> widgets,
|
||||
}) {
|
||||
return Object.hash(
|
||||
meta.storeRevision,
|
||||
identityHashCode(bank.transactions),
|
||||
Object.hashAll(widgets.map((w) => w.id)),
|
||||
Object.hashAll(widgets.map((w) => w.updatedAt)),
|
||||
_editingWidgets,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Inactive IndexedStack tabs must not watch providers — otherwise every
|
||||
// annotation save rebuilds Home while the user is on another tab.
|
||||
if (ShellTabScope.of(context) != HomeScreen.tabIndex) {
|
||||
final active = ShellTabScope.of(context) == HomeScreen.tabIndex;
|
||||
if (!active) {
|
||||
if (_work.isLoading) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (ShellTabScope.of(context) == HomeScreen.tabIndex) return;
|
||||
_work.cancel();
|
||||
});
|
||||
}
|
||||
return const SizedBox.expand();
|
||||
}
|
||||
|
||||
final bank = context.watch<BankController>();
|
||||
final meta = context.watch<TxMetaController>();
|
||||
final prefs = context.watch<PreferencesController>();
|
||||
final widgets = _editingWidgets
|
||||
? prefs.homeFilterWidgets
|
||||
: prefs.displayedHomeFilterWidgets;
|
||||
final needsData = bank.isLinked || bank.hasData;
|
||||
final emptyLoading = bank.isLoading && bank.balance == null && !bank.hasData;
|
||||
final cacheKey = _homeCacheKey(meta: meta, bank: bank, widgets: widgets);
|
||||
|
||||
if (needsData && !emptyLoading && !_work.readyFor(cacheKey)) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (ShellTabScope.of(context) != HomeScreen.tabIndex) return;
|
||||
_work.ensure(
|
||||
key: cacheKey,
|
||||
mounted: () => mounted,
|
||||
onUpdate: () {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
compute: (yieldUi) async {
|
||||
final categorized =
|
||||
meta.labeledTransactionCount(bank.transactions);
|
||||
await yieldUi();
|
||||
final stats = <String, TxFilterMetrics>{};
|
||||
final budgets = <String, HomeBudgetMetrics>{};
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
final w = widgets[i];
|
||||
if (w.type == HomeWidgetType.budget) {
|
||||
budgets[w.id] = computeHomeBudgetMetrics(
|
||||
all: bank.transactions,
|
||||
meta: meta,
|
||||
widget: w,
|
||||
);
|
||||
} else {
|
||||
stats[w.id] = computeTxFilterMetrics(
|
||||
all: bank.transactions,
|
||||
meta: meta,
|
||||
filter: w.filter,
|
||||
);
|
||||
}
|
||||
if (i % 2 == 1) await yieldUi();
|
||||
}
|
||||
return _HomeSnapshot(
|
||||
categorized: categorized,
|
||||
statsById: stats,
|
||||
budgetById: budgets,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
final showSpinner =
|
||||
needsData && !emptyLoading && !_work.readyFor(cacheKey);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -54,7 +145,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
onPressed: () =>
|
||||
setState(() => _editingWidgets = !_editingWidgets),
|
||||
icon: Icon(
|
||||
_editingWidgets ? Icons.check : Icons.dashboard_customize_outlined,
|
||||
_editingWidgets
|
||||
? Icons.check
|
||||
: Icons.dashboard_customize_outlined,
|
||||
),
|
||||
),
|
||||
...bankAppBarActions(
|
||||
@@ -67,9 +160,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
body: RefreshIndicator(
|
||||
onRefresh: bank.refresh,
|
||||
child: ResponsiveBody(
|
||||
child: _HomeBody(
|
||||
child: showSpinner
|
||||
? ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: const [
|
||||
SizedBox(height: 120),
|
||||
LoadingView(message: 'Loading home…'),
|
||||
],
|
||||
)
|
||||
: _HomeBody(
|
||||
bank: bank,
|
||||
editingWidgets: _editingWidgets,
|
||||
snapshot: _work.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -81,10 +183,12 @@ class _HomeBody extends StatelessWidget {
|
||||
const _HomeBody({
|
||||
required this.bank,
|
||||
required this.editingWidgets,
|
||||
required this.snapshot,
|
||||
});
|
||||
|
||||
final BankController bank;
|
||||
final bool editingWidgets;
|
||||
final _HomeSnapshot? snapshot;
|
||||
|
||||
void _openTransactions(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
@@ -141,7 +245,7 @@ class _HomeBody extends StatelessWidget {
|
||||
final colors = theme.colorScheme;
|
||||
final meta = context.watch<TxMetaController>();
|
||||
final recent = bank.transactions.take(5).toList(growable: false);
|
||||
final categorized =
|
||||
final categorized = snapshot?.categorized ??
|
||||
meta.labeledTransactionCount(bank.transactions);
|
||||
final widgets = editingWidgets
|
||||
? prefs.homeFilterWidgets
|
||||
@@ -319,6 +423,9 @@ class _HomeBody extends StatelessWidget {
|
||||
child: HomeFilterWidgetCard(
|
||||
widgetConfig: widgetConfig,
|
||||
editing: true,
|
||||
precomputedStats: snapshot?.statsById[widgetConfig.id],
|
||||
precomputedBudget:
|
||||
snapshot?.budgetById[widgetConfig.id],
|
||||
onToggleDisplay: (display) =>
|
||||
prefs.setHomeFilterWidgetDisplay(
|
||||
widgetConfig.id,
|
||||
@@ -347,7 +454,11 @@ class _HomeBody extends StatelessWidget {
|
||||
},
|
||||
)
|
||||
else
|
||||
..._packedHomeWidgetRows(widgets),
|
||||
..._packedHomeWidgetRows(
|
||||
widgets,
|
||||
statsById: snapshot?.statsById,
|
||||
budgetById: snapshot?.budgetById,
|
||||
),
|
||||
],
|
||||
if (_showBuiltIn(
|
||||
editing: editingWidgets,
|
||||
@@ -553,7 +664,11 @@ class _HomeWidgetSlot extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _packedHomeWidgetRows(List<HomeFilterWidget> widgets) {
|
||||
List<Widget> _packedHomeWidgetRows(
|
||||
List<HomeFilterWidget> widgets, {
|
||||
Map<String, TxFilterMetrics>? statsById,
|
||||
Map<String, HomeBudgetMetrics>? budgetById,
|
||||
}) {
|
||||
final rows = <Widget>[];
|
||||
var i = 0;
|
||||
while (i < widgets.length) {
|
||||
@@ -571,11 +686,19 @@ List<Widget> _packedHomeWidgetRows(List<HomeFilterWidget> widgets) {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: HomeFilterWidgetCard(widgetConfig: a),
|
||||
child: HomeFilterWidgetCard(
|
||||
widgetConfig: a,
|
||||
precomputedStats: statsById?[a.id],
|
||||
precomputedBudget: budgetById?[a.id],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: HomeFilterWidgetCard(widgetConfig: b),
|
||||
child: HomeFilterWidgetCard(
|
||||
widgetConfig: b,
|
||||
precomputedStats: statsById?[b.id],
|
||||
precomputedBudget: budgetById?[b.id],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -590,7 +713,11 @@ List<Widget> _packedHomeWidgetRows(List<HomeFilterWidget> widgets) {
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: _HomeWidgetSlot(
|
||||
halfWidth: a.halfWidth,
|
||||
child: HomeFilterWidgetCard(widgetConfig: a),
|
||||
child: HomeFilterWidgetCard(
|
||||
widgetConfig: a,
|
||||
precomputedStats: statsById?[a.id],
|
||||
precomputedBudget: budgetById?[a.id],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pocketbase/pocketbase.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -46,6 +47,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
email: _emailController.text,
|
||||
password: _passwordController.text,
|
||||
);
|
||||
// Lets iOS/Android password managers offer to save / confirm the login.
|
||||
TextInput.finishAutofillContext();
|
||||
if (!mounted) return;
|
||||
if (!widget.asRoot) {
|
||||
Navigator.of(context).pop(true);
|
||||
@@ -96,7 +99,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Form(
|
||||
AutofillGroup(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -106,6 +110,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
textInputAction: TextInputAction.next,
|
||||
autofocus: true,
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
AutofillHints.username,
|
||||
],
|
||||
validator: (value) {
|
||||
final text = value?.trim() ?? '';
|
||||
if (text.isEmpty) return 'Enter your email';
|
||||
@@ -118,6 +126,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
controller: _passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
textInputAction: TextInputAction.done,
|
||||
autofillHints: const [AutofillHints.password],
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
onFieldSubmitted: (_) => _submit(),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
@@ -160,6 +171,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/// Look ahead: projected balance from recurring templates through a date.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -16,6 +19,7 @@ import '../utils/look_ahead.dart';
|
||||
import '../utils/navigation.dart';
|
||||
import '../utils/tx_calendar.dart';
|
||||
import '../widgets/empty_state.dart';
|
||||
import '../widgets/loading_view.dart';
|
||||
import '../widgets/recurring_template_sheet.dart';
|
||||
import '../widgets/responsive_body.dart';
|
||||
import '../widgets/shell_tab_scope.dart';
|
||||
@@ -23,6 +27,25 @@ import '../widgets/skip_build_when_covered.dart';
|
||||
|
||||
enum _PeriodMode { monthly, yearly }
|
||||
|
||||
class _ItemsSnapshot {
|
||||
const _ItemsSnapshot({required this.key, required this.items});
|
||||
|
||||
final int key;
|
||||
final List<RecurringItem> items;
|
||||
}
|
||||
|
||||
class _BalancePoint {
|
||||
const _BalancePoint({
|
||||
required this.key,
|
||||
required this.label,
|
||||
required this.balance,
|
||||
});
|
||||
|
||||
final DateTime key;
|
||||
final String label;
|
||||
final double balance;
|
||||
}
|
||||
|
||||
class LookAheadScreen extends StatefulWidget {
|
||||
const LookAheadScreen({super.key});
|
||||
|
||||
@@ -35,8 +58,10 @@ class LookAheadScreen extends StatefulWidget {
|
||||
class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
var _period = _PeriodMode.monthly;
|
||||
late DateTime _cursor;
|
||||
List<RecurringItem>? _cachedItems;
|
||||
int? _itemsCacheKey;
|
||||
_ItemsSnapshot? _snapshot;
|
||||
int? _loadingKey;
|
||||
int _computeGen = 0;
|
||||
var _showChart = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -52,14 +77,15 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
|
||||
/// Inclusive end date for the selected period.
|
||||
DateTime get _through {
|
||||
if (_period == _PeriodMode.yearly) {
|
||||
// Chart view (and yearly list) always project through year-end.
|
||||
if (_showChart || _period == _PeriodMode.yearly) {
|
||||
return DateTime(_cursor.year, 12, 31);
|
||||
}
|
||||
return DateTime(_cursor.year, _cursor.month + 1, 0);
|
||||
}
|
||||
|
||||
String get _periodTitle {
|
||||
if (_period == _PeriodMode.yearly) return '${_cursor.year}';
|
||||
if (_showChart || _period == _PeriodMode.yearly) return '${_cursor.year}';
|
||||
return formatMonthYear(_cursor);
|
||||
}
|
||||
|
||||
@@ -68,28 +94,32 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
return DateTime(t.year + maxYears, t.month);
|
||||
}
|
||||
|
||||
int _maxYear(int maxYears) => _today.year + maxYears;
|
||||
|
||||
bool get _navByYear => _showChart || _period == _PeriodMode.yearly;
|
||||
|
||||
bool _canGoBack() {
|
||||
if (_period == _PeriodMode.yearly) {
|
||||
return _cursor.year > _today.year;
|
||||
}
|
||||
if (_navByYear) return _cursor.year > _today.year;
|
||||
return monthStart(_cursor).isAfter(_minMonth);
|
||||
}
|
||||
|
||||
bool _canGoForward(int maxYears) {
|
||||
if (_period == _PeriodMode.yearly) {
|
||||
return _cursor.year < _today.year + maxYears;
|
||||
}
|
||||
if (_navByYear) return _cursor.year < _maxYear(maxYears);
|
||||
return monthStart(_cursor).isBefore(_maxMonthFor(maxYears));
|
||||
}
|
||||
|
||||
void _clampCursor(int maxYears) {
|
||||
if (_period == _PeriodMode.yearly) {
|
||||
if (_cursor.year > _today.year + maxYears) {
|
||||
_cursor = DateTime(_today.year + maxYears);
|
||||
if (_navByYear) {
|
||||
if (_cursor.year > _maxYear(maxYears)) {
|
||||
_cursor = DateTime(_maxYear(maxYears));
|
||||
}
|
||||
if (_cursor.year < _today.year) {
|
||||
_cursor = DateTime(_today.year);
|
||||
}
|
||||
} else {
|
||||
final maxMonth = _maxMonthFor(maxYears);
|
||||
if (_cursor.isAfter(maxMonth)) _cursor = maxMonth;
|
||||
if (_cursor.isBefore(_minMonth)) _cursor = _minMonth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,26 +127,22 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
if (mode == _period) return;
|
||||
setState(() {
|
||||
_period = mode;
|
||||
if (mode == _PeriodMode.yearly) {
|
||||
if (mode == _PeriodMode.yearly || _showChart) {
|
||||
_cursor = DateTime(_cursor.year);
|
||||
if (_cursor.year < _today.year) _cursor = DateTime(_today.year);
|
||||
if (_cursor.year > _today.year + maxYears) {
|
||||
_cursor = DateTime(_today.year + maxYears);
|
||||
}
|
||||
_clampCursor(maxYears);
|
||||
} else {
|
||||
final month = DateTime(_cursor.year, _today.month);
|
||||
_cursor = month.isBefore(_minMonth) ? _minMonth : monthStart(month);
|
||||
final maxMonth = _maxMonthFor(maxYears);
|
||||
if (_cursor.isAfter(maxMonth)) _cursor = maxMonth;
|
||||
_clampCursor(maxYears);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _go(int delta, int maxYears) {
|
||||
setState(() {
|
||||
if (_period == _PeriodMode.yearly) {
|
||||
if (_navByYear) {
|
||||
final next = DateTime(_cursor.year + delta);
|
||||
if (next.year < _today.year || next.year > _today.year + maxYears) {
|
||||
if (next.year < _today.year || next.year > _maxYear(maxYears)) {
|
||||
return;
|
||||
}
|
||||
_cursor = next;
|
||||
@@ -129,9 +155,149 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleChart(int maxYears) {
|
||||
setState(() {
|
||||
_showChart = !_showChart;
|
||||
if (_showChart) {
|
||||
_cursor = DateTime(_cursor.year);
|
||||
} else if (_period == _PeriodMode.monthly) {
|
||||
final month = DateTime(_cursor.year, _today.month);
|
||||
_cursor = month.isBefore(_minMonth) ? _minMonth : monthStart(month);
|
||||
}
|
||||
_clampCursor(maxYears);
|
||||
});
|
||||
}
|
||||
|
||||
int _itemsCacheKey(TxMetaController meta, BankController bank) =>
|
||||
Object.hash(meta.storeRevision, identityHashCode(bank.transactions));
|
||||
|
||||
/// Projected account balance at each month-end / year-end.
|
||||
List<_BalancePoint> _balanceSeries({
|
||||
required List<RecurringItem> items,
|
||||
required double currentBalance,
|
||||
required int maxYears,
|
||||
}) {
|
||||
final today = _today;
|
||||
if (_period == _PeriodMode.monthly) {
|
||||
final year = _cursor.year;
|
||||
const months = [
|
||||
'J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D',
|
||||
];
|
||||
final out = <_BalancePoint>[];
|
||||
for (var m = 1; m <= 12; m++) {
|
||||
final end = DateTime(year, m + 1, 0);
|
||||
final balance = !end.isAfter(today)
|
||||
? currentBalance
|
||||
: predictBalance(
|
||||
currentBalance: currentBalance,
|
||||
from: today,
|
||||
throughDate: end,
|
||||
items: items,
|
||||
);
|
||||
out.add(
|
||||
_BalancePoint(
|
||||
key: DateTime(year, m),
|
||||
label: months[m - 1],
|
||||
balance: balance,
|
||||
),
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
final startYear = _today.year;
|
||||
final endYear = _maxYear(maxYears);
|
||||
final out = <_BalancePoint>[];
|
||||
for (var y = startYear; y <= endYear; y++) {
|
||||
final end = DateTime(y, 12, 31);
|
||||
final balance = !end.isAfter(today)
|
||||
? currentBalance
|
||||
: predictBalance(
|
||||
currentBalance: currentBalance,
|
||||
from: today,
|
||||
throughDate: end,
|
||||
items: items,
|
||||
);
|
||||
out.add(
|
||||
_BalancePoint(
|
||||
key: DateTime(y),
|
||||
label: '$y',
|
||||
balance: balance,
|
||||
),
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Resolve recurring amounts off the build path, yielding so nav stays live.
|
||||
void _ensureItemsLoaded({
|
||||
required int cacheKey,
|
||||
required TxMetaController meta,
|
||||
required BankController bank,
|
||||
}) {
|
||||
if (_snapshot?.key == cacheKey || _loadingKey == cacheKey) return;
|
||||
_loadingKey = cacheKey;
|
||||
final gen = ++_computeGen;
|
||||
final templates = List<RecurringTemplate>.of(meta.recurringTemplates);
|
||||
final transactions = bank.transactions;
|
||||
|
||||
unawaited(() async {
|
||||
// Let the spinner / tab switch paint before heavy work.
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!mounted || gen != _computeGen) return;
|
||||
|
||||
final items = <RecurringItem>[];
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
final tpl = templates[i];
|
||||
items.add(
|
||||
RecurringItem(
|
||||
templateId: tpl.id,
|
||||
description: tpl.name,
|
||||
amount: resolveRecurringAmount(
|
||||
template: tpl,
|
||||
transactions: transactions,
|
||||
isInternalTransfer: (tx) => meta.metaFor(tx).internalTransfer,
|
||||
labelIdsFor: meta.labelIdsFor,
|
||||
),
|
||||
currency: tpl.currency,
|
||||
dayOfMonth: tpl.dayOfMonth.clamp(1, 28),
|
||||
ruleSummary: recurringRuleSummary(
|
||||
template: tpl,
|
||||
labelName: (id) => meta.labelById(id)?.name ?? 'label',
|
||||
),
|
||||
sourceTxId: tpl.sourceTxId,
|
||||
labelIds: [
|
||||
if (tpl.labelId != null && tpl.labelId!.isNotEmpty) tpl.labelId!,
|
||||
],
|
||||
),
|
||||
);
|
||||
// Yield often so bottom-nav taps are not starved.
|
||||
if (i % 2 == 1) {
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!mounted || gen != _computeGen) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted || gen != _computeGen) return;
|
||||
setState(() {
|
||||
_snapshot = _ItemsSnapshot(key: cacheKey, items: items);
|
||||
if (_loadingKey == cacheKey) _loadingKey = null;
|
||||
});
|
||||
}());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (ShellTabScope.of(context) != LookAheadScreen.tabIndex) {
|
||||
final active = ShellTabScope.of(context) == LookAheadScreen.tabIndex;
|
||||
if (!active) {
|
||||
if (_loadingKey != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (ShellTabScope.of(context) == LookAheadScreen.tabIndex) return;
|
||||
_computeGen++;
|
||||
_loadingKey = null;
|
||||
});
|
||||
}
|
||||
return const SizedBox.expand();
|
||||
}
|
||||
return SkipBuildWhenCovered(
|
||||
@@ -165,22 +331,38 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
final currentAmount = balance?.amount ?? 0;
|
||||
final today = _today;
|
||||
final through = _through;
|
||||
final cacheKey = Object.hash(
|
||||
meta.storeRevision,
|
||||
identityHashCode(bank.transactions),
|
||||
);
|
||||
if (_cachedItems == null || _itemsCacheKey != cacheKey) {
|
||||
_itemsCacheKey = cacheKey;
|
||||
_cachedItems = recurringItemsFromTemplates(
|
||||
templates: meta.recurringTemplates,
|
||||
transactions: bank.transactions,
|
||||
isInternalTransfer: (tx) => meta.metaFor(tx).internalTransfer,
|
||||
labelIdsFor: meta.labelIdsFor,
|
||||
labelName: (id) => meta.labelById(id)?.name ?? 'label',
|
||||
final cacheKey = _itemsCacheKey(meta, bank);
|
||||
if (_snapshot?.key != cacheKey && _loadingKey != cacheKey) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (ShellTabScope.of(context) != LookAheadScreen.tabIndex) return;
|
||||
_ensureItemsLoaded(cacheKey: cacheKey, meta: meta, bank: bank);
|
||||
});
|
||||
}
|
||||
|
||||
final snapshot = _snapshot;
|
||||
final ready = snapshot != null && snapshot.key == cacheKey;
|
||||
|
||||
if (!ready) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Look ahead'),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: _showChart ? 'Show list' : 'Show chart',
|
||||
onPressed: () => _toggleChart(maxYears),
|
||||
icon: Icon(
|
||||
_showChart ? Icons.view_list_outlined : Icons.calendar_month,
|
||||
),
|
||||
),
|
||||
...bankAppBarActions(context: context),
|
||||
],
|
||||
),
|
||||
body: const LoadingView(message: 'Calculating look ahead…'),
|
||||
);
|
||||
}
|
||||
final items = _cachedItems!;
|
||||
|
||||
final items = snapshot.items;
|
||||
final projected = projectedOccurrences(
|
||||
items: items,
|
||||
from: today,
|
||||
@@ -216,7 +398,16 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Look ahead'),
|
||||
actions: bankAppBarActions(context: context),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: _showChart ? 'Show list' : 'Show chart',
|
||||
onPressed: () => _toggleChart(maxYears),
|
||||
icon: Icon(
|
||||
_showChart ? Icons.view_list_outlined : Icons.calendar_month,
|
||||
),
|
||||
),
|
||||
...bankAppBarActions(context: context),
|
||||
],
|
||||
),
|
||||
body: ResponsiveBody(
|
||||
child: ListView(
|
||||
@@ -234,10 +425,8 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
canGoForward: _canGoForward(maxYears),
|
||||
onBack: () => _go(-1, maxYears),
|
||||
onForward: () => _go(1, maxYears),
|
||||
backTooltip:
|
||||
_period == _PeriodMode.yearly ? 'Previous year' : 'Previous month',
|
||||
forwardTooltip:
|
||||
_period == _PeriodMode.yearly ? 'Next year' : 'Next month',
|
||||
backTooltip: _navByYear ? 'Previous year' : 'Previous month',
|
||||
forwardTooltip: _navByYear ? 'Next year' : 'Next month',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Material(
|
||||
@@ -321,6 +510,28 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_showChart) ...[
|
||||
const SizedBox(height: 16),
|
||||
_LookAheadBalanceChart(
|
||||
points: _balanceSeries(
|
||||
items: items,
|
||||
currentBalance: currentAmount,
|
||||
maxYears: maxYears,
|
||||
),
|
||||
period: _period,
|
||||
currency: currency,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_period == _PeriodMode.yearly
|
||||
? 'Projected account balance by year'
|
||||
: 'Projected account balance by month',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
@@ -362,6 +573,11 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_SavingsCard(
|
||||
monthlySavings: monthlyIncome - monthlyExpense,
|
||||
currency: currency,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (items.isEmpty)
|
||||
const EmptyState(
|
||||
icon: Icons.event_repeat_outlined,
|
||||
@@ -398,12 +614,322 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LookAheadBalanceChart extends StatelessWidget {
|
||||
const _LookAheadBalanceChart({
|
||||
required this.points,
|
||||
required this.period,
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<_BalancePoint> points;
|
||||
final _PeriodMode period;
|
||||
final String currency;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
if (points.isEmpty) {
|
||||
return Card(
|
||||
color: colors.surfaceContainer,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
'No projected balance for this period',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
var minY = points.first.balance;
|
||||
var maxY = points.first.balance;
|
||||
for (final p in points) {
|
||||
if (p.balance < minY) minY = p.balance;
|
||||
if (p.balance > maxY) maxY = p.balance;
|
||||
}
|
||||
if (minY == maxY) {
|
||||
minY -= 1;
|
||||
maxY += 1;
|
||||
}
|
||||
final pad = (maxY - minY).abs() * 0.12;
|
||||
final safeMin = minY - pad;
|
||||
final safeMax = maxY + pad;
|
||||
final interval = ((safeMax - safeMin) / 3).abs();
|
||||
final safeInterval = interval <= 0 ? 1.0 : interval;
|
||||
|
||||
final spots = [
|
||||
for (var i = 0; i < points.length; i++)
|
||||
FlSpot(i.toDouble(), points[i].balance),
|
||||
];
|
||||
|
||||
return Card(
|
||||
color: colors.surfaceContainer,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'Projected balance',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height: 220,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
minX: 0,
|
||||
maxX: (points.length - 1).toDouble(),
|
||||
minY: safeMin,
|
||||
maxY: safeMax,
|
||||
lineTouchData: LineTouchData(
|
||||
handleBuiltInTouches: true,
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipItems: (touched) {
|
||||
return [
|
||||
for (final spot in touched)
|
||||
if (spot.x.round() >= 0 &&
|
||||
spot.x.round() < points.length)
|
||||
LineTooltipItem(
|
||||
'${points[spot.x.round()].label}\n'
|
||||
'${formatMoney(points[spot.x.round()].balance, currency)}',
|
||||
theme.textTheme.labelMedium!.copyWith(
|
||||
color: colors.onInverseSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 44,
|
||||
interval: safeInterval,
|
||||
getTitlesWidget: (value, meta) {
|
||||
if ((value - safeMax).abs() < 0.01 ||
|
||||
(value - safeMin).abs() < 0.01) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Text(
|
||||
_compactMoney(value),
|
||||
style: theme.textTheme.labelSmall,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 28,
|
||||
interval: 1,
|
||||
getTitlesWidget: (value, meta) {
|
||||
final index = value.round();
|
||||
if (index < 0 || index >= points.length) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (period == _PeriodMode.yearly &&
|
||||
points.length > 8 &&
|
||||
index % 2 == 1) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
points[index].label,
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: safeInterval,
|
||||
getDrawingHorizontalLine: (value) => FlLine(
|
||||
color: colors.outlineVariant.withValues(alpha: 0.35),
|
||||
strokeWidth: 1,
|
||||
dashArray: const [4, 4],
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: spots,
|
||||
isCurved: true,
|
||||
curveSmoothness: 0.18,
|
||||
color: colors.primary,
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, bar, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 3.5,
|
||||
color: colors.primary,
|
||||
strokeWidth: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: colors.primary.withValues(alpha: 0.14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
duration: Duration.zero,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SavingsCard extends StatelessWidget {
|
||||
const _SavingsCard({
|
||||
required this.monthlySavings,
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final double monthlySavings;
|
||||
final String currency;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
final yearly = monthlySavings * 12;
|
||||
final positive = monthlySavings >= 0;
|
||||
final tone = positive ? AppTheme.accent : AppTheme.expense;
|
||||
|
||||
return Card(
|
||||
color: colors.surfaceContainer,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: tone.withValues(alpha: 0.18),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(Icons.savings_outlined, color: tone, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _SavingsMetric(
|
||||
label: 'Monthly savings',
|
||||
amount: formatMoney(monthlySavings, currency),
|
||||
color: tone,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _SavingsMetric(
|
||||
label: 'Yearly savings',
|
||||
amount: formatMoney(yearly, currency),
|
||||
color: tone,
|
||||
alignEnd: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SavingsMetric extends StatelessWidget {
|
||||
const _SavingsMetric({
|
||||
required this.label,
|
||||
required this.amount,
|
||||
required this.color,
|
||||
this.alignEnd = false,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String amount;
|
||||
final Color color;
|
||||
final bool alignEnd;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
alignEnd ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label.toUpperCase(),
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
letterSpacing: 0.6,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
amount,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _compactMoney(double value) {
|
||||
final abs = value.abs();
|
||||
if (abs >= 1000000) return '${(value / 1000000).toStringAsFixed(1)}M';
|
||||
if (abs >= 1000) return '${(value / 1000).toStringAsFixed(1)}k';
|
||||
if (abs >= 100) return value.toStringAsFixed(0);
|
||||
return value.toStringAsFixed(abs < 10 && abs > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
class _PeriodToolbar extends StatelessWidget {
|
||||
const _PeriodToolbar({
|
||||
required this.period,
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../providers/bank_provider.dart';
|
||||
import '../providers/preferences_provider.dart';
|
||||
import '../providers/tx_meta_provider.dart';
|
||||
import '../utils/app_theme.dart';
|
||||
import '../utils/deferred_tab_work.dart';
|
||||
import '../utils/helpers.dart';
|
||||
import '../utils/navigation.dart';
|
||||
import '../utils/tx_calendar.dart';
|
||||
@@ -28,6 +29,36 @@ import 'transactions_screen.dart';
|
||||
const _kIncomeColor = AppTheme.accent;
|
||||
const _kExpenseColor = AppTheme.expense;
|
||||
|
||||
class _FavouriteAgg {
|
||||
const _FavouriteAgg({
|
||||
required this.periods,
|
||||
required this.monthly,
|
||||
required this.labelSpend,
|
||||
required this.labelAmountByPeriod,
|
||||
});
|
||||
|
||||
final List<DateTime> periods;
|
||||
final Map<DateTime, _MonthBucket> monthly;
|
||||
final List<_LabelSpend> labelSpend;
|
||||
final Map<DateTime, Map<String, double>> labelAmountByPeriod;
|
||||
}
|
||||
|
||||
class _OverviewAggSnapshot {
|
||||
const _OverviewAggSnapshot({
|
||||
required this.periods,
|
||||
required this.monthly,
|
||||
required this.labelSpend,
|
||||
required this.labelAmountByPeriod,
|
||||
required this.favourites,
|
||||
});
|
||||
|
||||
final List<DateTime> periods;
|
||||
final Map<DateTime, _MonthBucket> monthly;
|
||||
final List<_LabelSpend> labelSpend;
|
||||
final Map<DateTime, Map<String, double>> labelAmountByPeriod;
|
||||
final Map<String, _FavouriteAgg> favourites;
|
||||
}
|
||||
|
||||
class OverviewScreen extends StatefulWidget {
|
||||
const OverviewScreen({super.key});
|
||||
|
||||
@@ -59,6 +90,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
/// Favourites charts view (vs full overview with history).
|
||||
bool _showFavouritesView = false;
|
||||
var _favouritesDefaultApplied = false;
|
||||
final _work = DeferredTabWork<_OverviewAggSnapshot>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -247,7 +279,15 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (ShellTabScope.of(context) != OverviewScreen.tabIndex) {
|
||||
final active = ShellTabScope.of(context) == OverviewScreen.tabIndex;
|
||||
if (!active) {
|
||||
if (_work.isLoading) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (ShellTabScope.of(context) == OverviewScreen.tabIndex) return;
|
||||
_work.cancel();
|
||||
});
|
||||
}
|
||||
return const SizedBox.expand();
|
||||
}
|
||||
|
||||
@@ -268,6 +308,41 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
final emptyLoading =
|
||||
bank.isLoading && bank.transactions.isEmpty && bank.balance == null;
|
||||
final needsData = bank.isLinked || bank.hasData;
|
||||
final displayedFavs = prefs.displayedOverviewFavourites;
|
||||
final cacheKey = Object.hash(
|
||||
meta.storeRevision,
|
||||
identityHashCode(bank.transactions),
|
||||
_showFavouritesView,
|
||||
_period,
|
||||
_metric,
|
||||
_cursor.year,
|
||||
Object.hashAll(_includedLabelIds),
|
||||
_labelFlow,
|
||||
_labelBarStyle,
|
||||
_labelInvertChart,
|
||||
Object.hashAll(displayedFavs.map((f) => f.id)),
|
||||
Object.hashAll(displayedFavs.map((f) => f.updatedAt)),
|
||||
);
|
||||
|
||||
if (needsData && !emptyLoading && !_work.readyFor(cacheKey)) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (ShellTabScope.of(context) != OverviewScreen.tabIndex) return;
|
||||
_scheduleOverviewCompute(
|
||||
key: cacheKey,
|
||||
bank: bank,
|
||||
meta: meta,
|
||||
displayedFavs: displayedFavs,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
final showSpinner =
|
||||
needsData && !emptyLoading && !_work.readyFor(cacheKey);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_showFavouritesView ? 'Favourites' : 'Overview'),
|
||||
@@ -288,7 +363,15 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
body: RefreshIndicator(
|
||||
onRefresh: bank.refresh,
|
||||
child: ResponsiveBody(
|
||||
child: SkipBuildWhenCovered(
|
||||
child: showSpinner
|
||||
? ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: const [
|
||||
SizedBox(height: 120),
|
||||
LoadingView(message: 'Loading overview…'),
|
||||
],
|
||||
)
|
||||
: SkipBuildWhenCovered(
|
||||
builder: (context) => _showFavouritesView
|
||||
? _buildFavouritesBody(context, bank, meta, prefs)
|
||||
: _buildBody(context, bank, meta, prefs),
|
||||
@@ -298,6 +381,89 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _scheduleOverviewCompute({
|
||||
required Object key,
|
||||
required BankController bank,
|
||||
required TxMetaController meta,
|
||||
required List<OverviewFavourite> displayedFavs,
|
||||
}) {
|
||||
_work.ensure(
|
||||
key: key,
|
||||
mounted: () => mounted,
|
||||
onUpdate: () {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
compute: (yieldUi) async {
|
||||
final txs = bank.transactions;
|
||||
final periods = _chartPeriods(txs);
|
||||
await yieldUi();
|
||||
|
||||
Map<DateTime, _MonthBucket> monthly = const {};
|
||||
var labelSpend = const <_LabelSpend>[];
|
||||
Map<DateTime, Map<String, double>> labelAmountByPeriod = const {};
|
||||
|
||||
if (!_showFavouritesView) {
|
||||
if (_metric == OverviewMetricMode.flow) {
|
||||
monthly = _flowTotals(txs, meta, periods, _period);
|
||||
} else {
|
||||
final labels = _labelAggregates(
|
||||
txs,
|
||||
meta,
|
||||
periods,
|
||||
_period,
|
||||
includedLabelIds: _includedLabelIds,
|
||||
flow: _labelFlow,
|
||||
);
|
||||
labelSpend = labels.spend;
|
||||
labelAmountByPeriod = labels.amountByPeriod;
|
||||
}
|
||||
await yieldUi();
|
||||
}
|
||||
|
||||
final favourites = <String, _FavouriteAgg>{};
|
||||
if (_showFavouritesView) {
|
||||
for (var i = 0; i < displayedFavs.length; i++) {
|
||||
final favourite = displayedFavs[i];
|
||||
final favPeriods = _chartPeriodsFor(favourite.period, txs);
|
||||
Map<DateTime, _MonthBucket> favMonthly = const {};
|
||||
var favLabels = const <_LabelSpend>[];
|
||||
Map<DateTime, Map<String, double>> favByPeriod = const {};
|
||||
if (favourite.metric == OverviewMetricMode.flow) {
|
||||
favMonthly =
|
||||
_flowTotals(txs, meta, favPeriods, favourite.period);
|
||||
} else {
|
||||
final labels = _labelAggregates(
|
||||
txs,
|
||||
meta,
|
||||
favPeriods,
|
||||
favourite.period,
|
||||
includedLabelIds: favourite.labelIds.toSet(),
|
||||
flow: favourite.flow,
|
||||
);
|
||||
favLabels = labels.spend;
|
||||
favByPeriod = labels.amountByPeriod;
|
||||
}
|
||||
favourites[favourite.id] = _FavouriteAgg(
|
||||
periods: favPeriods,
|
||||
monthly: favMonthly,
|
||||
labelSpend: favLabels,
|
||||
labelAmountByPeriod: favByPeriod,
|
||||
);
|
||||
await yieldUi();
|
||||
}
|
||||
}
|
||||
|
||||
return _OverviewAggSnapshot(
|
||||
periods: periods,
|
||||
monthly: monthly,
|
||||
labelSpend: labelSpend,
|
||||
labelAmountByPeriod: labelAmountByPeriod,
|
||||
favourites: favourites,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFavouritesBody(
|
||||
BuildContext context,
|
||||
BankController bank,
|
||||
@@ -329,6 +495,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
: 'EUR');
|
||||
final txs = bank.transactions;
|
||||
final displayed = prefs.displayedOverviewFavourites;
|
||||
final agg = _work.value;
|
||||
|
||||
return ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
@@ -399,6 +566,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
txs: txs,
|
||||
meta: meta,
|
||||
currency: currency,
|
||||
precomputed: agg?.favourites[displayed[i].id],
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -412,12 +580,16 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
required List<BankTransaction> txs,
|
||||
required TxMetaController meta,
|
||||
required String currency,
|
||||
_FavouriteAgg? precomputed,
|
||||
}) {
|
||||
final periods = _chartPeriodsFor(favourite.period, txs);
|
||||
Map<DateTime, _MonthBucket> monthly = const {};
|
||||
var labelSpend = const <_LabelSpend>[];
|
||||
Map<DateTime, Map<String, double>> labelAmountByPeriod = const {};
|
||||
final periods = precomputed?.periods ??
|
||||
_chartPeriodsFor(favourite.period, txs);
|
||||
Map<DateTime, _MonthBucket> monthly = precomputed?.monthly ?? const {};
|
||||
var labelSpend = precomputed?.labelSpend ?? const <_LabelSpend>[];
|
||||
Map<DateTime, Map<String, double>> labelAmountByPeriod =
|
||||
precomputed?.labelAmountByPeriod ?? const {};
|
||||
|
||||
if (precomputed == null) {
|
||||
if (favourite.metric == OverviewMetricMode.flow) {
|
||||
monthly = _flowTotals(txs, meta, periods, favourite.period);
|
||||
} else {
|
||||
@@ -432,6 +604,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
labelSpend = labels.spend;
|
||||
labelAmountByPeriod = labels.amountByPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
final periodTitle = favourite.period == OverviewPeriodMode.monthly
|
||||
? '${_cursor.year}'
|
||||
@@ -487,6 +660,16 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
: null),
|
||||
periodMode: favourite.period,
|
||||
),
|
||||
onChartTap: () => _openChartFilter(
|
||||
context,
|
||||
labelIds: {
|
||||
for (final id in favourite.labelIds) id,
|
||||
},
|
||||
flow: favourite.metric == OverviewMetricMode.labels
|
||||
? favourite.flow
|
||||
: null,
|
||||
periodMode: favourite.period,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -520,15 +703,17 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
? bank.transactions.first.currency
|
||||
: 'EUR');
|
||||
final txs = bank.transactions;
|
||||
final periods = _chartPeriods(txs);
|
||||
final agg = _work.value;
|
||||
final periods = agg?.periods ?? _chartPeriods(txs);
|
||||
final historyPeriods = [...periods.reversed];
|
||||
|
||||
// Only aggregate what the active metric needs — toggling was scanning
|
||||
// all txs 3–4 times and resolving full meta each time.
|
||||
Map<DateTime, _MonthBucket> monthly = const {};
|
||||
var labelSpend = const <_LabelSpend>[];
|
||||
Map<DateTime, Map<String, double>> labelAmountByPeriod = const {};
|
||||
// Prefer deferred aggregates; fall back only if snapshot is missing.
|
||||
Map<DateTime, _MonthBucket> monthly = agg?.monthly ?? const {};
|
||||
var labelSpend = agg?.labelSpend ?? const <_LabelSpend>[];
|
||||
Map<DateTime, Map<String, double>> labelAmountByPeriod =
|
||||
agg?.labelAmountByPeriod ?? const {};
|
||||
|
||||
if (agg == null) {
|
||||
if (_metric == OverviewMetricMode.flow) {
|
||||
monthly = _flowTotals(txs, meta, periods, _period);
|
||||
} else {
|
||||
@@ -543,6 +728,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
labelSpend = labels.spend;
|
||||
labelAmountByPeriod = labels.amountByPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
final summaryFlow = _metric == OverviewMetricMode.flow
|
||||
? _sumBuckets(monthly.values)
|
||||
@@ -609,6 +795,10 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
flow: flow ??
|
||||
(_metric == OverviewMetricMode.labels ? _labelFlow : null),
|
||||
),
|
||||
onChartTap: () => _openChartFilter(
|
||||
context,
|
||||
flow: _metric == OverviewMetricMode.labels ? _labelFlow : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
if (_metric == OverviewMetricMode.flow) ...[
|
||||
@@ -767,10 +957,12 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
final resolvedLabelIds = labelId != null
|
||||
? {labelId}
|
||||
: (labelIds ??
|
||||
{
|
||||
(_metric == OverviewMetricMode.labels
|
||||
? {
|
||||
// Open with the same labels currently shown on the chart.
|
||||
for (final id in _includedLabelIds) id,
|
||||
});
|
||||
}
|
||||
: <String>{}));
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => TransactionsScreen(
|
||||
@@ -778,12 +970,41 @@ class _OverviewScreenState extends State<OverviewScreen> {
|
||||
initialLabelIds: resolvedLabelIds,
|
||||
initialLabelMatch: LabelMatchMode.or,
|
||||
initialFlow: flow,
|
||||
initialSearchInMonthOnly: !yearly && resolvedLabelIds.isNotEmpty,
|
||||
// A specific bar/period should always scope to that month (or year).
|
||||
initialSearchInMonthOnly: !yearly,
|
||||
initialYear: yearly ? period.year : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Open Transactions with this chart's filters (no specific bar/column).
|
||||
void _openChartFilter(
|
||||
BuildContext context, {
|
||||
Set<String>? labelIds,
|
||||
TxFlowFilter? flow,
|
||||
OverviewPeriodMode? periodMode,
|
||||
}) {
|
||||
final mode = periodMode ?? _period;
|
||||
final yearly = mode == OverviewPeriodMode.yearly;
|
||||
final resolvedLabelIds = labelIds ??
|
||||
{
|
||||
for (final id in _includedLabelIds) id,
|
||||
};
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => TransactionsScreen(
|
||||
// Monthly charts show a year — open that whole year with the chart filter.
|
||||
initialMonth: yearly ? null : DateTime(_cursor.year, 12),
|
||||
initialYear: yearly ? null : _cursor.year,
|
||||
initialLabelIds: resolvedLabelIds,
|
||||
initialLabelMatch: LabelMatchMode.or,
|
||||
initialFlow: flow,
|
||||
initialSearchInMonthOnly: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MonthBucket {
|
||||
@@ -1183,6 +1404,7 @@ class _ChartCard extends StatelessWidget {
|
||||
required this.availableLabelCount,
|
||||
required this.onLabelTap,
|
||||
required this.onMonthTap,
|
||||
required this.onChartTap,
|
||||
this.onPeriodModeChanged,
|
||||
this.onEdit,
|
||||
});
|
||||
@@ -1211,6 +1433,7 @@ class _ChartCard extends StatelessWidget {
|
||||
String? labelId,
|
||||
TxFlowFilter? flow,
|
||||
}) onMonthTap;
|
||||
final VoidCallback onChartTap;
|
||||
final ValueChanged<OverviewPeriodMode>? onPeriodModeChanged;
|
||||
final VoidCallback? onEdit;
|
||||
|
||||
@@ -1387,13 +1610,15 @@ class _ChartCard extends StatelessWidget {
|
||||
final hasLegend = metric == OverviewMetricMode.flow ||
|
||||
labelSpend.isNotEmpty;
|
||||
|
||||
final radius = BorderRadius.circular(compact ? 16 : 20);
|
||||
|
||||
return Card(
|
||||
color: colors.surfaceContainer,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(compact ? 16 : 20),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(borderRadius: radius),
|
||||
// Header/legend open the chart-wide filter; bars handle their own taps.
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
compact ? 12 : 14,
|
||||
@@ -1401,6 +1626,13 @@ class _ChartCard extends StatelessWidget {
|
||||
compact ? 10 : 14,
|
||||
compact ? 12 : 16,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onChartTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Column(
|
||||
children: [
|
||||
if (compact) ...[
|
||||
@@ -1549,6 +1781,10 @@ class _ChartCard extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: chartHeight,
|
||||
child: metric == OverviewMetricMode.flow
|
||||
@@ -1558,6 +1794,7 @@ class _ChartCard extends StatelessWidget {
|
||||
currency: currency,
|
||||
period: period,
|
||||
onMonthTap: onMonthTap,
|
||||
onChartTap: onChartTap,
|
||||
)
|
||||
: _LabelMonthBars(
|
||||
months: months,
|
||||
@@ -1568,6 +1805,7 @@ class _ChartCard extends StatelessWidget {
|
||||
barStyle: labelBarStyle,
|
||||
invertChart: invertChart,
|
||||
onMonthTap: onMonthTap,
|
||||
onChartTap: onChartTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1584,6 +1822,7 @@ class _FlowBars extends StatelessWidget {
|
||||
required this.currency,
|
||||
required this.period,
|
||||
required this.onMonthTap,
|
||||
required this.onChartTap,
|
||||
});
|
||||
|
||||
final List<DateTime> months;
|
||||
@@ -1595,6 +1834,7 @@ class _FlowBars extends StatelessWidget {
|
||||
String? labelId,
|
||||
TxFlowFilter? flow,
|
||||
}) onMonthTap;
|
||||
final VoidCallback onChartTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -1613,7 +1853,10 @@ class _FlowBars extends StatelessWidget {
|
||||
});
|
||||
|
||||
if (empty) {
|
||||
return Center(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onChartTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
period == OverviewPeriodMode.yearly
|
||||
? 'No income or expenses in these years'
|
||||
@@ -1621,6 +1864,7 @@ class _FlowBars extends StatelessWidget {
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1629,36 +1873,35 @@ class _FlowBars extends StatelessWidget {
|
||||
maxY: safeMax,
|
||||
groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10,
|
||||
barTouchData: BarTouchData(
|
||||
handleBuiltInTouches: false,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) => null,
|
||||
),
|
||||
touchCallback: (event, response) {
|
||||
if (event is! FlTapUpEvent) return;
|
||||
final spot = response?.spot;
|
||||
if (spot == null) return;
|
||||
if (spot == null) {
|
||||
onChartTap();
|
||||
return;
|
||||
}
|
||||
final index = spot.touchedBarGroupIndex;
|
||||
if (index < 0 || index >= months.length) return;
|
||||
final rodIndex = spot.touchedRodDataIndex;
|
||||
// Rods: 0 = expense, 1 = income.
|
||||
final flow = rodIndex == 1
|
||||
if (index < 0 || index >= months.length) {
|
||||
onChartTap();
|
||||
return;
|
||||
}
|
||||
final rod = spot.touchedRodDataIndex;
|
||||
// Rod 0 = expense, rod 1 = income (see barGroups below).
|
||||
final flow = rod == 0
|
||||
? TxFlowFilter.expense
|
||||
: rod == 1
|
||||
? TxFlowFilter.income
|
||||
: TxFlowFilter.expense;
|
||||
: null;
|
||||
onMonthTap(months[index], flow: flow);
|
||||
},
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
final month = months[group.x.toInt()];
|
||||
final bucket = monthly[_periodKey(month, period)]!;
|
||||
final isIncome = rodIndex == 1;
|
||||
final value = isIncome ? bucket.income : bucket.expense;
|
||||
return BarTooltipItem(
|
||||
'${_periodAxisLabel(month, period)}\n'
|
||||
'${isIncome ? 'Income' : 'Expense'}\n'
|
||||
'${formatMoney(value, currency)}',
|
||||
TextStyle(color: colors.onInverseSurface),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
topTitles:
|
||||
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
rightTitles:
|
||||
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
leftTitles: AxisTitles(
|
||||
@@ -1755,6 +1998,7 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
required this.barStyle,
|
||||
required this.invertChart,
|
||||
required this.onMonthTap,
|
||||
required this.onChartTap,
|
||||
});
|
||||
|
||||
static const _maxSeriesLabels = 6;
|
||||
@@ -1771,6 +2015,7 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
String? labelId,
|
||||
TxFlowFilter? flow,
|
||||
}) onMonthTap;
|
||||
final VoidCallback onChartTap;
|
||||
|
||||
double _plot(double value) => invertChart ? -value : value;
|
||||
|
||||
@@ -1779,12 +2024,16 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
if (labelSpend.isEmpty) {
|
||||
return Center(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onChartTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No labeled activity in this period.\nTag transactions to see them here.',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1841,12 +2090,16 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
if (maxY == 0 && minY == 0) {
|
||||
return Center(
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onChartTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No labeled activity in this period.\nTag transactions to see them here.',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final span = (maxY - minY).abs();
|
||||
@@ -1946,67 +2199,40 @@ class _LabelMonthBars extends StatelessWidget {
|
||||
maxY: safeMax,
|
||||
groupsSpace: period == OverviewPeriodMode.yearly ? 16 : 10,
|
||||
barTouchData: BarTouchData(
|
||||
handleBuiltInTouches: false,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) => null,
|
||||
),
|
||||
touchCallback: (event, response) {
|
||||
if (event is! FlTapUpEvent) return;
|
||||
final spot = response?.spot;
|
||||
if (spot == null) return;
|
||||
if (spot == null) {
|
||||
onChartTap();
|
||||
return;
|
||||
}
|
||||
final index = spot.touchedBarGroupIndex;
|
||||
if (index < 0 || index >= months.length) return;
|
||||
String? labelId;
|
||||
if (index < 0 || index >= months.length) {
|
||||
onChartTap();
|
||||
return;
|
||||
}
|
||||
final month = months[index];
|
||||
if (stacked) {
|
||||
final stackIdx = spot.touchedStackItemIndex;
|
||||
if (stackIdx >= 0 && stackIdx < topLabels.length) {
|
||||
labelId = topLabels[stackIdx].label.id;
|
||||
final stackIndex = spot.touchedStackItemIndex;
|
||||
if (stackIndex >= 0 && stackIndex < topLabels.length) {
|
||||
onMonthTap(month, labelId: topLabels[stackIndex].label.id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
final rodIdx = spot.touchedRodDataIndex;
|
||||
if (rodIdx >= 0 && rodIdx < topLabels.length) {
|
||||
labelId = topLabels[rodIdx].label.id;
|
||||
// "Other" segment or whole column → that period with chart labels.
|
||||
onMonthTap(month);
|
||||
return;
|
||||
}
|
||||
// Last rod may be "Other" — open month with chart labels only.
|
||||
final rod = spot.touchedRodDataIndex;
|
||||
if (rod >= 0 && rod < topLabels.length) {
|
||||
onMonthTap(month, labelId: topLabels[rod].label.id);
|
||||
return;
|
||||
}
|
||||
onMonthTap(months[index], labelId: labelId);
|
||||
onMonthTap(month);
|
||||
},
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
final month = months[group.x.toInt()];
|
||||
if (stacked && rod.rodStackItems.isNotEmpty) {
|
||||
final bucket =
|
||||
amountByMonth[_periodKey(month, period)] ?? const {};
|
||||
final buffer = StringBuffer(
|
||||
'${_periodAxisLabel(month, period)}\n',
|
||||
);
|
||||
var total = 0.0;
|
||||
for (final row in topLabels) {
|
||||
final amount = bucket[row.label.id] ?? 0;
|
||||
if (amount == 0) continue;
|
||||
total += amount;
|
||||
buffer.writeln(
|
||||
'${row.label.name}: ${formatMoney(amount, currency)}',
|
||||
);
|
||||
}
|
||||
final other = otherFor(month);
|
||||
if (other != 0) {
|
||||
total += other;
|
||||
buffer.writeln('Other: ${formatMoney(other, currency)}');
|
||||
}
|
||||
buffer.write('Total: ${formatMoney(total, currency)}');
|
||||
return BarTooltipItem(
|
||||
buffer.toString(),
|
||||
TextStyle(color: colors.onInverseSurface),
|
||||
);
|
||||
}
|
||||
final name = rodIndex < topLabels.length
|
||||
? topLabels[rodIndex].label.name
|
||||
: 'Other';
|
||||
return BarTooltipItem(
|
||||
'${_periodAxisLabel(month, period)}\n'
|
||||
'$name\n'
|
||||
'${formatMoney(invertChart ? -rod.toY : rod.toY, currency)}',
|
||||
TextStyle(color: colors.onInverseSurface),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
|
||||
@@ -188,6 +188,7 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
|
||||
await meta.setLabelsForMany(
|
||||
targets,
|
||||
_draftLabelIds.toList(growable: false),
|
||||
matchPattern: toMatched ? _pattern : null,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
|
||||
@@ -27,7 +27,31 @@ bool isSameTransaction(BankTransaction a, BankTransaction b) {
|
||||
if (a.date != b.date) return false;
|
||||
if (a.currency.toUpperCase() != b.currency.toUpperCase()) return false;
|
||||
if (_amountCents(a.amount) != _amountCents(b.amount)) return false;
|
||||
return _descriptionsLikelyMatch(a, b);
|
||||
}
|
||||
|
||||
/// True when a cached/API [pending] row is the unsettled form of [booked].
|
||||
///
|
||||
/// Banks often change booking date (and sometimes wording) when a payment
|
||||
/// settles, so this allows a small date window unlike [isSameTransaction].
|
||||
bool isPendingSettledBy(
|
||||
BankTransaction pending,
|
||||
BankTransaction booked, {
|
||||
int maxDayGap = 10,
|
||||
}) {
|
||||
if (!pending.pending || booked.pending) return false;
|
||||
if (pending.isManual || booked.isManual) return false;
|
||||
if (pending.currency.toUpperCase() != booked.currency.toUpperCase()) {
|
||||
return false;
|
||||
}
|
||||
if (_amountCents(pending.amount) != _amountCents(booked.amount)) return false;
|
||||
if (!_descriptionsLikelyMatch(pending, booked)) return false;
|
||||
final gap = _dateDiffDays(pending.date, booked.date);
|
||||
if (gap == null) return false;
|
||||
return gap.abs() <= maxDayGap;
|
||||
}
|
||||
|
||||
bool _descriptionsLikelyMatch(BankTransaction a, BankTransaction b) {
|
||||
if (a.descriptionKey == b.descriptionKey) return true;
|
||||
|
||||
final ma = a.merchantKey;
|
||||
@@ -47,10 +71,62 @@ bool isSameTransaction(BankTransaction a, BankTransaction b) {
|
||||
|
||||
int _amountCents(double amount) => (amount * 100).round();
|
||||
|
||||
int? _dateDiffDays(String a, String b) {
|
||||
final da = DateTime.tryParse(a);
|
||||
final db = DateTime.tryParse(b);
|
||||
if (da == null || db == null) return null;
|
||||
return da.difference(db).inDays;
|
||||
}
|
||||
|
||||
/// Coarse bucket so fuzzy [isSameTransaction] only compares likely peers.
|
||||
String _dedupeBucket(BankTransaction tx) =>
|
||||
'${tx.date}|${tx.currency.toUpperCase()}|${_amountCents(tx.amount)}';
|
||||
|
||||
String _amountCurrencyBucket(BankTransaction tx) =>
|
||||
'${tx.currency.toUpperCase()}|${_amountCents(tx.amount)}';
|
||||
|
||||
/// Drop pending rows that already have a booked twin (same money + merchant).
|
||||
List<BankTransaction> dropSettledPendingDuplicates(
|
||||
List<BankTransaction> txs,
|
||||
) {
|
||||
final booked = [for (final tx in txs) if (!tx.pending) tx];
|
||||
if (booked.isEmpty) return txs;
|
||||
|
||||
final bookedBuckets = <String, List<BankTransaction>>{};
|
||||
for (final tx in booked) {
|
||||
bookedBuckets.putIfAbsent(_amountCurrencyBucket(tx), () => []).add(tx);
|
||||
}
|
||||
|
||||
final out = <BankTransaction>[];
|
||||
for (final tx in txs) {
|
||||
if (!tx.pending || tx.isManual) {
|
||||
out.add(tx);
|
||||
continue;
|
||||
}
|
||||
final peers = bookedBuckets[_amountCurrencyBucket(tx)];
|
||||
final settled =
|
||||
peers != null && peers.any((b) => isPendingSettledBy(tx, b));
|
||||
if (!settled) out.add(tx);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Merge a fresh bank API page with locally cached history.
|
||||
///
|
||||
/// Pending rows are ephemeral: only the current API list may keep them
|
||||
/// (except admin manuals). That stops settled/cancelled pendings from
|
||||
/// living forever in the on-device cache after the bank drops them.
|
||||
List<BankTransaction> mergeApiTransactionsWithCache(
|
||||
List<BankTransaction> apiTxs,
|
||||
List<BankTransaction> cached,
|
||||
) {
|
||||
final history = [
|
||||
for (final tx in cached)
|
||||
if (!tx.pending || tx.isManual) tx,
|
||||
];
|
||||
return dropSettledPendingDuplicates(mergeTransactions(apiTxs, history));
|
||||
}
|
||||
|
||||
/// Merge [extra] into [existing], skipping duplicates. Newest date first.
|
||||
List<BankTransaction> mergeTransactions(
|
||||
List<BankTransaction> existing,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/// Schedule heavy tab work off the first paint, yielding so nav stays responsive.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
/// Runs [compute] after a frame, cancellable via [cancel] / generation bumps.
|
||||
class DeferredTabWork<T> {
|
||||
int _gen = 0;
|
||||
Object? _loadingKey;
|
||||
T? value;
|
||||
Object? valueKey;
|
||||
|
||||
bool readyFor(Object key) => value != null && valueKey == key;
|
||||
|
||||
bool get isLoading => _loadingKey != null;
|
||||
|
||||
void cancel() {
|
||||
_gen++;
|
||||
_loadingKey = null;
|
||||
}
|
||||
|
||||
/// Starts work for [key] unless already ready or in flight for that key.
|
||||
void ensure({
|
||||
required Object key,
|
||||
required Future<T> Function(Future<void> Function() yieldUi) compute,
|
||||
required void Function() onUpdate,
|
||||
required bool Function() mounted,
|
||||
}) {
|
||||
if (readyFor(key) || _loadingKey == key) return;
|
||||
_loadingKey = key;
|
||||
final gen = ++_gen;
|
||||
|
||||
unawaited(() async {
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!mounted() || gen != _gen) return;
|
||||
|
||||
Future<void> yieldUi() async {
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
}
|
||||
|
||||
final result = await compute(yieldUi);
|
||||
if (!mounted() || gen != _gen) return;
|
||||
|
||||
value = result;
|
||||
valueKey = key;
|
||||
if (_loadingKey == key) _loadingKey = null;
|
||||
onUpdate();
|
||||
}());
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ class AppTextField extends StatelessWidget {
|
||||
this.keyboardType,
|
||||
this.validator,
|
||||
this.autofocus = false,
|
||||
this.autofillHints,
|
||||
this.obscureText = false,
|
||||
this.enableSuggestions,
|
||||
this.autocorrect,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
@@ -24,6 +28,10 @@ class AppTextField extends StatelessWidget {
|
||||
final TextInputType? keyboardType;
|
||||
final String? Function(String?)? validator;
|
||||
final bool autofocus;
|
||||
final Iterable<String>? autofillHints;
|
||||
final bool obscureText;
|
||||
final bool? enableSuggestions;
|
||||
final bool? autocorrect;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -31,8 +39,12 @@ class AppTextField extends StatelessWidget {
|
||||
controller: controller,
|
||||
maxLines: maxLines,
|
||||
autofocus: autofocus,
|
||||
obscureText: obscureText,
|
||||
textInputAction: textInputAction,
|
||||
keyboardType: keyboardType,
|
||||
autofillHints: autofillHints,
|
||||
enableSuggestions: enableSuggestions ?? !obscureText,
|
||||
autocorrect: autocorrect ?? !obscureText,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
|
||||
@@ -23,6 +23,8 @@ class HomeFilterWidgetCard extends StatelessWidget {
|
||||
this.onToggleDisplay,
|
||||
this.onEdit,
|
||||
this.dragHandle,
|
||||
this.precomputedStats,
|
||||
this.precomputedBudget,
|
||||
});
|
||||
|
||||
final HomeFilterWidget widgetConfig;
|
||||
@@ -31,6 +33,10 @@ class HomeFilterWidgetCard extends StatelessWidget {
|
||||
final VoidCallback? onEdit;
|
||||
final Widget? dragHandle;
|
||||
|
||||
/// When set, skips scanning all transactions in build.
|
||||
final TxFilterMetrics? precomputedStats;
|
||||
final HomeBudgetMetrics? precomputedBudget;
|
||||
|
||||
void _openTransactions(BuildContext context) {
|
||||
final filter = widgetConfig.filter;
|
||||
Navigator.of(context).push(
|
||||
@@ -95,12 +101,14 @@ class HomeFilterWidgetCard extends StatelessWidget {
|
||||
currency: currency,
|
||||
transactions: bank.transactions,
|
||||
meta: meta,
|
||||
precomputed: precomputedBudget,
|
||||
)
|
||||
: _StatsBody(
|
||||
widgetConfig: widgetConfig,
|
||||
currency: currency,
|
||||
transactions: bank.transactions,
|
||||
meta: meta,
|
||||
precomputed: precomputedStats,
|
||||
),
|
||||
),
|
||||
if (editing && onToggleDisplay != null)
|
||||
@@ -267,18 +275,21 @@ class _StatsBody extends StatelessWidget {
|
||||
required this.currency,
|
||||
required this.transactions,
|
||||
required this.meta,
|
||||
this.precomputed,
|
||||
});
|
||||
|
||||
final HomeFilterWidget widgetConfig;
|
||||
final String currency;
|
||||
final List<BankTransaction> transactions;
|
||||
final TxMetaController meta;
|
||||
final TxFilterMetrics? precomputed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
final metrics = computeTxFilterMetrics(
|
||||
final metrics = precomputed ??
|
||||
computeTxFilterMetrics(
|
||||
all: transactions,
|
||||
meta: meta,
|
||||
filter: widgetConfig.filter,
|
||||
@@ -383,18 +394,21 @@ class _BudgetBody extends StatelessWidget {
|
||||
required this.currency,
|
||||
required this.transactions,
|
||||
required this.meta,
|
||||
this.precomputed,
|
||||
});
|
||||
|
||||
final HomeFilterWidget widgetConfig;
|
||||
final String currency;
|
||||
final List<BankTransaction> transactions;
|
||||
final TxMetaController meta;
|
||||
final HomeBudgetMetrics? precomputed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
final budget = computeHomeBudgetMetrics(
|
||||
final budget = precomputed ??
|
||||
computeHomeBudgetMetrics(
|
||||
all: transactions,
|
||||
meta: meta,
|
||||
widget: widgetConfig,
|
||||
|
||||
@@ -93,10 +93,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.3"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -356,10 +356,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
version: "3.2.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -465,10 +465,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
version: "1.12.2"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -58,4 +58,71 @@ Datum,Naam / Omschrijving,Rekening,Tegenrekening,Code,Af Bij,Bedrag (EUR),Mutati
|
||||
);
|
||||
expect(isSameTransaction(api, csv), isTrue);
|
||||
});
|
||||
|
||||
test('API merge drops stale pending not returned by the bank', () {
|
||||
const stalePending = BankTransaction(
|
||||
date: '2026-09-01',
|
||||
description: 'AH CAMPUS DIEMEN',
|
||||
amount: -12.5,
|
||||
currency: 'EUR',
|
||||
pending: true,
|
||||
);
|
||||
const settled = BankTransaction(
|
||||
date: '2026-09-03',
|
||||
description: 'BCK*AH CAMPUS DIEMEN ZUID NLD',
|
||||
amount: -12.5,
|
||||
currency: 'EUR',
|
||||
pending: false,
|
||||
);
|
||||
const olderBooked = BankTransaction(
|
||||
date: '2026-08-01',
|
||||
description: 'SALARIS',
|
||||
amount: 2000,
|
||||
currency: 'EUR',
|
||||
pending: false,
|
||||
);
|
||||
|
||||
final merged = mergeApiTransactionsWithCache(
|
||||
[settled],
|
||||
[stalePending, olderBooked],
|
||||
);
|
||||
|
||||
expect(merged, hasLength(2));
|
||||
expect(merged.any((tx) => tx.pending), isFalse);
|
||||
expect(merged.any((tx) => tx.description.contains('SALARIS')), isTrue);
|
||||
expect(merged.any((tx) => tx.description.contains('AH CAMPUS')), isTrue);
|
||||
});
|
||||
|
||||
test('API merge keeps live pending from the feed', () {
|
||||
const livePending = BankTransaction(
|
||||
date: '2026-09-10',
|
||||
description: 'PENDING COFFEE',
|
||||
amount: -4.0,
|
||||
currency: 'EUR',
|
||||
pending: true,
|
||||
);
|
||||
final merged = mergeApiTransactionsWithCache([livePending], const []);
|
||||
expect(merged, hasLength(1));
|
||||
expect(merged.single.pending, isTrue);
|
||||
});
|
||||
|
||||
test('dropSettledPendingDuplicates removes pending twin of booked', () {
|
||||
const pending = BankTransaction(
|
||||
date: '2026-09-01',
|
||||
description: 'AH CAMPUS DIEMEN',
|
||||
amount: -12.5,
|
||||
currency: 'EUR',
|
||||
pending: true,
|
||||
);
|
||||
const booked = BankTransaction(
|
||||
date: '2026-09-02',
|
||||
description: 'BCK*AH CAMPUS DIEMEN ZUID',
|
||||
amount: -12.5,
|
||||
currency: 'EUR',
|
||||
pending: false,
|
||||
);
|
||||
final cleaned = dropSettledPendingDuplicates([pending, booked]);
|
||||
expect(cleaned, hasLength(1));
|
||||
expect(cleaned.single.pending, isFalse);
|
||||
});
|
||||
}
|
||||
|
||||