diff --git a/.vscode/launch.json b/.vscode/launch.json
index 2d15047..af48daf 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
- "name": "Launch development",
+ "name": "Debug mode (development flavor)",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
@@ -17,18 +17,17 @@
]
},
{
- "name": "Launch staging",
+ "name": "Release mode (development flavor)",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
- "args": ["--flavor", "staging", "--target", "lib/main.dart"]
- },
- {
- "name": "Launch production (release)",
- "request": "launch",
- "type": "dart",
- "program": "lib/main.dart",
- "args": ["--release", "--flavor", "production", "--target", "lib/main.dart"]
+ "args": [
+ "--release",
+ "--flavor",
+ "development",
+ "--target",
+ "lib/main.dart"
+ ]
}
]
-}
+}
\ No newline at end of file
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 1d6abb4..c9ff353 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -1,5 +1,5 @@
-import java.util.Properties
import java.io.FileInputStream
+import java.util.Properties
plugins {
id("com.android.application")
@@ -10,6 +10,7 @@ plugins {
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
+
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
@@ -24,9 +25,7 @@ android {
targetCompatibility = JavaVersion.VERSION_11
}
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_11.toString()
- }
+ kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() }
defaultConfig {
applicationId = "dk.cafeanalog.cafe_analog_app"
@@ -45,7 +44,6 @@ android {
keyAlias = System.getenv("ANDROID_KEYSTORE_ALIAS")
keyPassword = System.getenv("ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD")
storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
-
} else {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
@@ -60,17 +58,12 @@ android {
create("production") {
dimension = "default"
applicationIdSuffix = ""
- manifestPlaceholders["appName"] = "Router Test App"
- }
- create("staging") {
- dimension = "default"
- applicationIdSuffix = ".stg"
- manifestPlaceholders["appName"] = "[STG] Router Test App"
+ manifestPlaceholders["appName"] = "Analog"
}
create("development") {
dimension = "default"
applicationIdSuffix = ".dev"
- manifestPlaceholders["appName"] = "[DEV] Router Test App"
+ manifestPlaceholders["appName"] = "[DEV] Analog"
}
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2aa7639..6188c4d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -29,9 +29,10 @@
-
+
+
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index 592ceee..e7efa9e 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1 +1,3 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
+#include "Flavors.xcconfig"
diff --git a/ios/Flutter/Flavors.xcconfig b/ios/Flutter/Flavors.xcconfig
new file mode 100644
index 0000000..4f0dcb2
--- /dev/null
+++ b/ios/Flutter/Flavors.xcconfig
@@ -0,0 +1,19 @@
+// Flavor-specific settings
+// This file is included from Debug.xcconfig and Release.xcconfig
+// and uses conditional variable expansion based on FLAVOR
+
+// Default values (production)
+DEEP_LINK_SCHEME = cafeanalog
+FLAVOR_APP_NAME = Analog
+
+// Override for development
+DEEP_LINK_SCHEME[config=Debug-development] = cafeanalog-dev
+DEEP_LINK_SCHEME[config=Release-development] = cafeanalog-dev
+FLAVOR_APP_NAME[config=Debug-development] = [DEV] Analog
+FLAVOR_APP_NAME[config=Release-development] = [DEV] Analog
+
+// Override for production
+DEEP_LINK_SCHEME[config=Debug-production] = cafeanalog
+DEEP_LINK_SCHEME[config=Release-production] = cafeanalog
+FLAVOR_APP_NAME[config=Debug-production] = Analog
+FLAVOR_APP_NAME[config=Release-production] = Analog
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 592ceee..03b4e8c 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1 +1,3 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+#include "Flavors.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..620e46e
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,43 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '13.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
new file mode 100644
index 0000000..37cd4a7
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,23 @@
+PODS:
+ - Flutter (1.0.0)
+ - flutter_secure_storage_darwin (10.0.0):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ flutter_secure_storage_darwin:
+ :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
+
+SPEC CHECKSUMS:
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+ flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
+
+PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
+
+COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index c951814..a8c8485 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -10,6 +10,8 @@
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 */; };
+ 63948AF0203226AA037B02A7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE33F2789F19B6B8FEB138F3 /* Pods_Runner.framework */; };
+ 689D96EFA946DED300D35B69 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEE1A362A0C0D30812C4D90 /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@@ -40,14 +42,22 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 03ECF82432B0030D22E8EFBA /* Pods-RunnerTests.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release-production.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release-production.xcconfig"; sourceTree = ""; };
+ 0FC98E59B2EDE6BAA5641967 /* Pods-RunnerTests.profile-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile-production.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile-production.xcconfig"; sourceTree = ""; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3774EE6D4F299149ADDD1F12 /* Pods-RunnerTests.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug-production.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug-production.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 48465233F49C55A3C6A940A8 /* Pods-Runner.profile-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-production.xcconfig"; sourceTree = ""; };
+ 5E33BD643C70C7A5C312484D /* Pods-RunnerTests.profile-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile-development.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile-development.xcconfig"; sourceTree = ""; };
+ 62F47DACAC15067B81C0398D /* Pods-RunnerTests.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release-development.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release-development.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 798694C28E76551E89B46CE1 /* Pods-RunnerTests.debug-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug-development.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug-development.xcconfig"; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 905101D50D3B9DC8B3AC3A13 /* Pods-Runner.debug-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-development.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -55,13 +65,28 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 99BEC39E0A63C94E504944CE /* Pods-Runner.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig"; sourceTree = ""; };
+ 9EEE1A362A0C0D30812C4D90 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ AE33F2789F19B6B8FEB138F3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ C4387A9ACE0701B08D96D92E /* Pods-Runner.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-development.xcconfig"; sourceTree = ""; };
+ E94BEC860F4078E9F45629D1 /* Pods-Runner.profile-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-development.xcconfig"; sourceTree = ""; };
+ ED89494CA39A23003B91C1D4 /* Pods-Runner.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 46C9CE9B67EBEB43359ADDD3 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 689D96EFA946DED300D35B69 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 63948AF0203226AA037B02A7 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -76,6 +101,35 @@
path = RunnerTests;
sourceTree = "";
};
+ 450CF66000B4BCC7D5386198 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 99BEC39E0A63C94E504944CE /* Pods-Runner.debug-production.xcconfig */,
+ 905101D50D3B9DC8B3AC3A13 /* Pods-Runner.debug-development.xcconfig */,
+ ED89494CA39A23003B91C1D4 /* Pods-Runner.release-production.xcconfig */,
+ C4387A9ACE0701B08D96D92E /* Pods-Runner.release-development.xcconfig */,
+ 48465233F49C55A3C6A940A8 /* Pods-Runner.profile-production.xcconfig */,
+ E94BEC860F4078E9F45629D1 /* Pods-Runner.profile-development.xcconfig */,
+ 3774EE6D4F299149ADDD1F12 /* Pods-RunnerTests.debug-production.xcconfig */,
+ 798694C28E76551E89B46CE1 /* Pods-RunnerTests.debug-development.xcconfig */,
+ 03ECF82432B0030D22E8EFBA /* Pods-RunnerTests.release-production.xcconfig */,
+ 62F47DACAC15067B81C0398D /* Pods-RunnerTests.release-development.xcconfig */,
+ 0FC98E59B2EDE6BAA5641967 /* Pods-RunnerTests.profile-production.xcconfig */,
+ 5E33BD643C70C7A5C312484D /* Pods-RunnerTests.profile-development.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 5154D0D635A201910C77961C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ AE33F2789F19B6B8FEB138F3 /* Pods_Runner.framework */,
+ 9EEE1A362A0C0D30812C4D90 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -94,6 +148,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
+ 450CF66000B4BCC7D5386198 /* Pods */,
+ 5154D0D635A201910C77961C /* Frameworks */,
);
sourceTree = "";
};
@@ -128,8 +184,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
+ ECD2C73C0DC060BBF91135DE /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
+ 46C9CE9B67EBEB43359ADDD3 /* Frameworks */,
);
buildRules = (
);
@@ -145,12 +203,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 1920A2473F2D197698E1E4FF /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 2E1FC85F3C7853B1DEAF0CE9 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -222,6 +282,45 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 1920A2473F2D197698E1E4FF /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 2E1FC85F3C7853B1DEAF0CE9 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -253,6 +352,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ ECD2C73C0DC060BBF91135DE /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -367,7 +488,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 87PNV8S7CN;
ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "Router Test App";
+ FLAVOR_APP_NAME = Analog;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -385,6 +506,7 @@
};
3C9C551B2B07AC99000E5FCD /* Debug-production */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 3774EE6D4F299149ADDD1F12 /* Pods-RunnerTests.debug-production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -461,7 +583,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 87PNV8S7CN;
ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "Router Test App";
+ FLAVOR_APP_NAME = Analog;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -478,6 +600,7 @@
};
3C9C551E2B07ACA4000E5FCD /* Release-production */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 03ECF82432B0030D22E8EFBA /* Pods-RunnerTests.release-production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -550,7 +673,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 87PNV8S7CN;
ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "Router Test App";
+ FLAVOR_APP_NAME = Analog;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -567,6 +690,7 @@
};
3C9C55242B07ACBD000E5FCD /* Profile-production */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 0FC98E59B2EDE6BAA5641967 /* Pods-RunnerTests.profile-production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -644,7 +768,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 87PNV8S7CN;
ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "[DEV] Router Test App";
+ FLAVOR_APP_NAME = "[DEV] Analog";
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -662,6 +786,7 @@
};
3C9C55272B07ACE7000E5FCD /* Debug-development */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 798694C28E76551E89B46CE1 /* Pods-RunnerTests.debug-development.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -736,7 +861,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 87PNV8S7CN;
ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "[DEV] Router Test App";
+ FLAVOR_APP_NAME = "[DEV] Analog";
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -753,6 +878,7 @@
};
3C9C552D2B07AD09000E5FCD /* Profile-development */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 5E33BD643C70C7A5C312484D /* Pods-RunnerTests.profile-development.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -827,7 +953,7 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 87PNV8S7CN;
ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "[DEV] Router Test App";
+ FLAVOR_APP_NAME = "[DEV] Analog";
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -844,6 +970,7 @@
};
3C9C55302B07AD1B000E5FCD /* Release-development */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 62F47DACAC15067B81C0398D /* Pods-RunnerTests.release-development.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -857,283 +984,6 @@
};
name = "Release-development";
};
- 3C9C55312B07AD26000E5FCD /* Debug-staging */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- 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;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = "Debug-staging";
- };
- 3C9C55322B07AD26000E5FCD /* Debug-staging */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg";
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 87PNV8S7CN;
- ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "[STG] Router Test App";
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PACKAGE_CONFIG = .dart_tool/package_config.json;
- PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.router-test-app.stg";
- 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-staging";
- };
- 3C9C55332B07AD26000E5FCD /* Debug-staging */ = {
- 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.example.verygoodcore.router-test-app.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-staging";
- };
- 3C9C55342B07AD41000E5FCD /* Release-staging */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- 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;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = "Release-staging";
- };
- 3C9C55352B07AD41000E5FCD /* Release-staging */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg";
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 87PNV8S7CN;
- ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "[STG] Router Test App";
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PACKAGE_CONFIG = .dart_tool/package_config.json;
- PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.router-test-app.stg";
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = "Release-staging";
- };
- 3C9C55362B07AD41000E5FCD /* Release-staging */ = {
- 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.example.verygoodcore.router-test-app.RunnerTests";
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = "Release-staging";
- };
- 3C9C55372B07AD4B000E5FCD /* Profile-staging */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- 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;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = "Profile-staging";
- };
- 3C9C55382B07AD4B000E5FCD /* Profile-staging */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg";
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 87PNV8S7CN;
- ENABLE_BITCODE = NO;
- FLAVOR_APP_NAME = "[STG] Router Test App";
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PACKAGE_CONFIG = .dart_tool/package_config.json;
- PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.router-test-app.stg";
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = "Profile-staging";
- };
- 3C9C55392B07AD4B000E5FCD /* Profile-staging */ = {
- 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.example.verygoodcore.router-test-app.RunnerTests";
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = "Profile-staging";
- };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1141,13 +991,10 @@
isa = XCConfigurationList;
buildConfigurations = (
3C9C551B2B07AC99000E5FCD /* Debug-production */,
- 3C9C55332B07AD26000E5FCD /* Debug-staging */,
3C9C55272B07ACE7000E5FCD /* Debug-development */,
3C9C551E2B07ACA4000E5FCD /* Release-production */,
- 3C9C55362B07AD41000E5FCD /* Release-staging */,
3C9C55302B07AD1B000E5FCD /* Release-development */,
3C9C55242B07ACBD000E5FCD /* Profile-production */,
- 3C9C55392B07AD4B000E5FCD /* Profile-staging */,
3C9C552D2B07AD09000E5FCD /* Profile-development */,
);
defaultConfigurationIsVisible = 0;
@@ -1157,13 +1004,10 @@
isa = XCConfigurationList;
buildConfigurations = (
3C9C55192B07AC99000E5FCD /* Debug-production */,
- 3C9C55312B07AD26000E5FCD /* Debug-staging */,
3C9C55252B07ACE7000E5FCD /* Debug-development */,
3C9C551C2B07ACA4000E5FCD /* Release-production */,
- 3C9C55342B07AD41000E5FCD /* Release-staging */,
3C9C552E2B07AD1B000E5FCD /* Release-development */,
3C9C55222B07ACBD000E5FCD /* Profile-production */,
- 3C9C55372B07AD4B000E5FCD /* Profile-staging */,
3C9C552B2B07AD09000E5FCD /* Profile-development */,
);
defaultConfigurationIsVisible = 0;
@@ -1173,13 +1017,10 @@
isa = XCConfigurationList;
buildConfigurations = (
3C9C551A2B07AC99000E5FCD /* Debug-production */,
- 3C9C55322B07AD26000E5FCD /* Debug-staging */,
3C9C55262B07ACE7000E5FCD /* Debug-development */,
3C9C551D2B07ACA4000E5FCD /* Release-production */,
- 3C9C55352B07AD41000E5FCD /* Release-staging */,
3C9C552F2B07AD1B000E5FCD /* Release-development */,
3C9C55232B07ACBD000E5FCD /* Profile-production */,
- 3C9C55382B07AD4B000E5FCD /* Profile-staging */,
3C9C552C2B07AD09000E5FCD /* Profile-development */,
);
defaultConfigurationIsVisible = 0;
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme
deleted file mode 100644
index 6e3b252..0000000
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 25236cf..968b30f 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -7,6 +7,19 @@
en
es
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ dk.cafeanalog
+ CFBundleURLSchemes
+
+ $(DEEP_LINK_SCHEME)
+
+
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
diff --git a/lib/app.dart b/lib/app.dart
deleted file mode 100644
index 35e4635..0000000
--- a/lib/app.dart
+++ /dev/null
@@ -1,272 +0,0 @@
-import 'package:cafe_analog_app/login/login_screen.dart';
-import 'package:cafe_analog_app/login/secret_page.dart';
-import 'package:cafe_analog_app/receipts/receipts_screen.dart';
-import 'package:cafe_analog_app/redeem_voucher/redeem_voucher_screen.dart';
-import 'package:cafe_analog_app/settings/settings_screen.dart';
-import 'package:cafe_analog_app/settings/your_profile_screen.dart';
-import 'package:cafe_analog_app/stats/view/stats_screen.dart';
-import 'package:cafe_analog_app/tickets/buy_tickets/buy_tickets_screen.dart';
-import 'package:cafe_analog_app/tickets/buy_tickets/product.dart';
-import 'package:cafe_analog_app/tickets/buy_tickets/ticket_detail_screen.dart';
-import 'package:cafe_analog_app/tickets/my_tickets/tickets_screen.dart';
-import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
-
-final goRouter = GoRouter(
- initialLocation: '/tickets',
- debugLogDiagnostics: true,
- routes: [
- GoRoute(
- path: '/login',
- pageBuilder: (context, state) => const MaterialPage(child: LoginScreen()),
- ),
- GoRoute(
- path: '/verify-mobilepay/:id',
- pageBuilder: (_, state) => MaterialPage(
- child: SecretScreen(id: state.pathParameters['id']!),
- ),
- ),
- StatefulShellRoute.indexedStack(
- builder: (context, state, navigationShell) {
- return ScaffoldWithNestedNavigation(navigationShell: navigationShell);
- },
- branches: [
- StatefulShellBranch(
- routes: [
- GoRoute(
- path: '/tickets',
- pageBuilder: (context, state) => const NoTransitionPage(
- child: TicketsScreen(),
- ),
- routes: [
- GoRoute(
- path: 'buy',
- builder: (_, _) => const BuyTicketsScreen(),
- routes: [
- GoRoute(
- path: 'ticket/:id',
- pageBuilder: (context, state) {
- // we don't use id here, but in a real app you might
- // fetch the product details based on the id
- // we pass the whole product via extra for simplicity
- //
- // cast state.extra to Product
- final product = state.extra! as Product;
- return MaterialPage(
- fullscreenDialog: true,
- child: TicketDetailScreen(product: product),
- );
- },
- ),
- ],
- ),
- GoRoute(
- path: 'redeem_voucher',
- pageBuilder: (context, state) => const MaterialPage(
- child: RedeemVoucherScreen(),
- ),
- ),
- ],
- ),
- ],
- ),
- StatefulShellBranch(
- routes: [
- GoRoute(
- path: '/receipts',
- pageBuilder: (context, state) => const NoTransitionPage(
- child: ReceiptsScreen(),
- ),
- routes: [
- GoRoute(
- path: 'purchase_receipt/:id',
- builder: (context, state) =>
- Container(), // TODO(marfavi): Implement receipt screen
- ),
- GoRoute(
- path: 'swipe_receipt/:id',
- builder: (context, state) =>
- Container(), // TODO(marfavi): Implement receipt screen
- ),
- ],
- ),
- ],
- ),
- StatefulShellBranch(
- routes: [
- GoRoute(
- path: '/stats',
- pageBuilder: (context, state) =>
- const NoTransitionPage(child: StatsScreen()),
- ),
- ],
- ),
- StatefulShellBranch(
- routes: [
- GoRoute(
- path: '/settings',
- pageBuilder: (context, state) =>
- const NoTransitionPage(child: SettingsScreen()),
- routes: [
- GoRoute(
- path: 'your-profile',
- pageBuilder: (context, state) =>
- const MaterialPage(child: YourProfileScreen()),
- ),
- ],
- ),
- ],
- ),
- ],
- ),
- ],
-);
-
-class App extends StatefulWidget {
- const App({super.key});
-
- @override
- State createState() => _AppState();
-}
-
-class _AppState extends State {
- // TODO(marfavi): Remove theme switching when all widgets support system theme
- Brightness _brightness = Brightness.light;
-
- void _setBrightness(Brightness brightness) {
- setState(() {
- _brightness = brightness;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp.router(
- routerConfig: goRouter,
- theme: ThemeData(
- brightness: _brightness,
- colorScheme: ColorScheme.fromSeed(
- brightness: _brightness,
- // brightness: Brightness.dark,
- // dynamicSchemeVariant: DynamicSchemeVariant.vibrant,
- seedColor: const Color(0xFF785B38),
- // seedColor: const Color(0xFF362619), // GOOD
- // primary: const Color(0xFF785B38),
- // surface: const Color(0xFFF9F8F2),
- // surfaceContainer: const Color(0xFFF0E8D8),
- // secondaryContainer: const Color(0xFFE9D4B7),
- ),
- ),
- builder: (context, child) {
- return AppBrightnessProvider(
- onBrightnessChanged: _setBrightness,
- child: child!,
- );
- },
- );
- }
-}
-
-class AppBrightnessProvider extends InheritedWidget {
- const AppBrightnessProvider({
- required this.onBrightnessChanged,
- required super.child,
- super.key,
- });
-
- final void Function(Brightness) onBrightnessChanged;
-
- static AppBrightnessProvider of(BuildContext context) {
- final result = context
- .dependOnInheritedWidgetOfExactType();
- assert(result != null, 'No AppBrightnessProvider found in context');
- return result!;
- }
-
- @override
- bool updateShouldNotify(AppBrightnessProvider oldWidget) {
- return onBrightnessChanged != oldWidget.onBrightnessChanged;
- }
-}
-
-class ScaffoldWithNestedNavigation extends StatelessWidget {
- const ScaffoldWithNestedNavigation({
- required this.navigationShell,
- super.key,
- });
-
- final StatefulNavigationShell navigationShell;
-
- void _goBranch(int index) {
- navigationShell.goBranch(
- index,
- // A common pattern when using bottom navigation bars is to support
- // navigating to the initial location when tapping the item that is
- // already active. This example demonstrates how to support this behavior,
- // using the initialLocation parameter of goBranch.
- initialLocation: index == navigationShell.currentIndex,
- );
- }
-
- @override
- Widget build(BuildContext context) {
- return LayoutBuilder(
- builder: (context, constraints) {
- return ScaffoldWithNavigationBar(
- body: navigationShell,
- selectedIndex: navigationShell.currentIndex,
- onDestinationSelected: _goBranch,
- );
- },
- );
- }
-}
-
-class ScaffoldWithNavigationBar extends StatelessWidget {
- const ScaffoldWithNavigationBar({
- required this.body,
- required this.selectedIndex,
- required this.onDestinationSelected,
- super.key,
- });
- final Widget body;
- final int selectedIndex;
- final ValueChanged onDestinationSelected;
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: body,
- bottomNavigationBar: NavigationBar(
- selectedIndex: selectedIndex,
- destinations: [
- NavigationDestination(
- label: 'Tickets',
- icon: selectedIndex == 0
- ? const Icon(Icons.confirmation_num)
- : const Icon(Icons.confirmation_num_outlined),
- ),
- NavigationDestination(
- label: 'Receipts',
- icon: selectedIndex == 1
- ? const Icon(Icons.receipt)
- : const Icon(Icons.receipt_outlined),
- ),
- NavigationDestination(
- label: 'Stats',
- icon: selectedIndex == 2
- ? const Icon(Icons.leaderboard)
- : const Icon(Icons.leaderboard_outlined),
- ),
- NavigationDestination(
- label: 'Settings',
- icon: selectedIndex == 3
- ? const Icon(Icons.settings)
- : const Icon(Icons.settings_outlined),
- ),
- ],
- onDestinationSelected: onDestinationSelected,
- ),
- );
- }
-}
diff --git a/lib/app/app.dart b/lib/app/app.dart
new file mode 100644
index 0000000..7747e1f
--- /dev/null
+++ b/lib/app/app.dart
@@ -0,0 +1,42 @@
+import 'package:cafe_analog_app/app/app_brightness_provider.dart';
+import 'package:cafe_analog_app/app/dependencies_provider.dart';
+import 'package:cafe_analog_app/app/router.dart';
+import 'package:flutter/material.dart';
+
+class App extends StatefulWidget {
+ const App({super.key});
+
+ @override
+ State createState() => _AppState();
+}
+
+class _AppState extends State {
+ // TODO(marfavi): Remove theme switching when all widgets support system theme
+ Brightness _brightness = Brightness.light;
+
+ void _setBrightness(Brightness brightness) {
+ setState(() => _brightness = brightness);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return DependenciesProvider(
+ child: MaterialApp.router(
+ routerConfig: AnalogGoRouter.instance.goRouter,
+ theme: ThemeData(
+ brightness: _brightness,
+ colorScheme: ColorScheme.fromSeed(
+ brightness: _brightness,
+ seedColor: const Color(0xFF785B38),
+ ),
+ ),
+ builder: (context, child) {
+ return AppBrightnessProvider(
+ onBrightnessChanged: _setBrightness,
+ child: child!,
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/app/app_brightness_provider.dart b/lib/app/app_brightness_provider.dart
new file mode 100644
index 0000000..4d3b8a1
--- /dev/null
+++ b/lib/app/app_brightness_provider.dart
@@ -0,0 +1,23 @@
+import 'package:flutter/material.dart';
+
+class AppBrightnessProvider extends InheritedWidget {
+ const AppBrightnessProvider({
+ required this.onBrightnessChanged,
+ required super.child,
+ super.key,
+ });
+
+ final void Function(Brightness) onBrightnessChanged;
+
+ static AppBrightnessProvider of(BuildContext context) {
+ final result = context
+ .dependOnInheritedWidgetOfExactType();
+ assert(result != null, 'No AppBrightnessProvider found in context');
+ return result!;
+ }
+
+ @override
+ bool updateShouldNotify(AppBrightnessProvider oldWidget) {
+ return onBrightnessChanged != oldWidget.onBrightnessChanged;
+ }
+}
diff --git a/lib/bootstrap.dart b/lib/app/bootstrap.dart
similarity index 94%
rename from lib/bootstrap.dart
rename to lib/app/bootstrap.dart
index 558f731..ed46c28 100644
--- a/lib/bootstrap.dart
+++ b/lib/app/bootstrap.dart
@@ -27,6 +27,8 @@ Future bootstrap(FutureOr Function() builder) async {
Bloc.observer = const AppBlocObserver();
+ WidgetsFlutterBinding.ensureInitialized();
+
// Add cross-flavor configuration here
runApp(await builder());
diff --git a/lib/app/dependencies_provider.dart b/lib/app/dependencies_provider.dart
new file mode 100644
index 0000000..dd57090
--- /dev/null
+++ b/lib/app/dependencies_provider.dart
@@ -0,0 +1,56 @@
+import 'dart:async';
+
+import 'package:cafe_analog_app/core/http_client.dart';
+import 'package:cafe_analog_app/core/network_request_executor.dart';
+import 'package:cafe_analog_app/login/bloc/authentication_cubit.dart';
+import 'package:cafe_analog_app/login/data/authentication_token_repository.dart';
+import 'package:cafe_analog_app/login/data/login_repository.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:logger/logger.dart';
+
+/// Provides the dependencies required throughout the app.
+class DependenciesProvider extends StatelessWidget {
+ const DependenciesProvider({required this.child, super.key});
+
+ final MaterialApp child;
+
+ @override
+ Widget build(BuildContext context) {
+ return MultiRepositoryProvider(
+ providers: [
+ RepositoryProvider(create: (_) => const FlutterSecureStorage()),
+ RepositoryProvider.value(value: apiV1),
+ RepositoryProvider.value(value: apiV2),
+ RepositoryProvider(create: (_) => Logger()),
+ RepositoryProvider(
+ create: (context) => NetworkRequestExecutor(logger: context.read()),
+ ),
+ RepositoryProvider(
+ create: (context) =>
+ LoginRepository(apiV2: context.read(), executor: context.read()),
+ ),
+ RepositoryProvider(
+ create: (context) =>
+ AuthTokenRepository(secureStorage: context.read()),
+ ),
+ ],
+ child: MultiBlocProvider(
+ providers: [
+ BlocProvider(
+ create: (context) {
+ final authCubit = AuthCubit(
+ authTokenRepository: context.read(),
+ loginRepository: context.read(),
+ );
+ unawaited(authCubit.start());
+ return authCubit;
+ },
+ ),
+ ],
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/lib/app/navigation_scaffolds.dart b/lib/app/navigation_scaffolds.dart
new file mode 100644
index 0000000..2d2cd2d
--- /dev/null
+++ b/lib/app/navigation_scaffolds.dart
@@ -0,0 +1,84 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class ScaffoldWithNestedNavigation extends StatelessWidget {
+ const ScaffoldWithNestedNavigation({
+ required this.navigationShell,
+ super.key,
+ });
+
+ final StatefulNavigationShell navigationShell;
+
+ void _goBranch(int index) {
+ navigationShell.goBranch(
+ index,
+ // A common pattern when using bottom navigation bars is to support
+ // navigating to the initial location when tapping the item that is
+ // already active. This example demonstrates how to support this behavior,
+ // using the initialLocation parameter of goBranch.
+ initialLocation: index == navigationShell.currentIndex,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ return ScaffoldWithNavigationBar(
+ body: navigationShell,
+ selectedIndex: navigationShell.currentIndex,
+ onDestinationSelected: _goBranch,
+ );
+ },
+ );
+ }
+}
+
+class ScaffoldWithNavigationBar extends StatelessWidget {
+ const ScaffoldWithNavigationBar({
+ required this.body,
+ required this.selectedIndex,
+ required this.onDestinationSelected,
+ super.key,
+ });
+ final Widget body;
+ final int selectedIndex;
+ final ValueChanged onDestinationSelected;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: body,
+ bottomNavigationBar: NavigationBar(
+ selectedIndex: selectedIndex,
+ destinations: [
+ NavigationDestination(
+ label: 'Tickets',
+ icon: selectedIndex == 0
+ ? const Icon(Icons.confirmation_num)
+ : const Icon(Icons.confirmation_num_outlined),
+ ),
+ NavigationDestination(
+ label: 'Receipts',
+ icon: selectedIndex == 1
+ ? const Icon(Icons.receipt)
+ : const Icon(Icons.receipt_outlined),
+ ),
+ NavigationDestination(
+ label: 'Stats',
+ icon: selectedIndex == 2
+ ? const Icon(Icons.leaderboard)
+ : const Icon(Icons.leaderboard_outlined),
+ ),
+ NavigationDestination(
+ label: 'Settings',
+ icon: selectedIndex == 3
+ ? const Icon(Icons.settings)
+ : const Icon(Icons.settings_outlined),
+ ),
+ ],
+ onDestinationSelected: onDestinationSelected,
+ ),
+ );
+ }
+}
diff --git a/lib/app/router.dart b/lib/app/router.dart
new file mode 100644
index 0000000..ac5dbe7
--- /dev/null
+++ b/lib/app/router.dart
@@ -0,0 +1,278 @@
+import 'dart:async';
+
+import 'package:cafe_analog_app/app/navigation_scaffolds.dart';
+import 'package:cafe_analog_app/app/splash_screen.dart';
+import 'package:cafe_analog_app/login/bloc/authentication_cubit.dart';
+import 'package:cafe_analog_app/login/ui/authentication_navigator.dart';
+import 'package:cafe_analog_app/login/ui/email_sent_screen.dart';
+import 'package:cafe_analog_app/login/ui/login_screen.dart';
+import 'package:cafe_analog_app/login/ui/verify_magic_link_screen.dart';
+import 'package:cafe_analog_app/receipts/receipts_screen.dart';
+import 'package:cafe_analog_app/redeem_voucher/redeem_voucher_screen.dart';
+import 'package:cafe_analog_app/settings/settings_screen.dart';
+import 'package:cafe_analog_app/settings/your_profile_screen.dart';
+import 'package:cafe_analog_app/stats/view/stats_screen.dart';
+import 'package:cafe_analog_app/tickets/buy_tickets/buy_tickets_screen.dart';
+import 'package:cafe_analog_app/tickets/buy_tickets/product.dart';
+import 'package:cafe_analog_app/tickets/buy_tickets/ticket_detail_screen.dart';
+import 'package:cafe_analog_app/tickets/my_tickets/tickets_screen.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+
+class AnalogGoRouter {
+ AnalogGoRouter._internal();
+
+ static final AnalogGoRouter instance = AnalogGoRouter._internal();
+
+ late final goRouter = GoRouter(
+ initialLocation: '/',
+ debugLogDiagnostics: kDebugMode,
+ routes: routes,
+ onEnter: onEnter,
+ redirect: redirect,
+ );
+
+ late final routes = [
+ // Root shell that listens to auth state changes
+ ShellRoute(
+ builder: (_, _, child) => AuthNavigator(child: child),
+ routes: [
+ // Splash screen shown at app start
+ GoRoute(
+ path: '/',
+ pageBuilder: (_, _) => const NoTransitionPage(child: SplashScreen()),
+ ),
+ GoRoute(
+ path: '/login',
+ pageBuilder: (_, _) => CustomTransitionPage(
+ child: const LoginScreen(),
+ transitionsBuilder: (_, animation, _, child) {
+ return FadeTransition(opacity: animation, child: child);
+ },
+ ),
+ routes: [
+ GoRoute(
+ path: 'email-sent',
+ pageBuilder: (_, state) {
+ final email = state.uri.queryParameters['email'] ?? '';
+ return MaterialPage(child: EmailSentScreen(email: email));
+ },
+ ),
+ GoRoute(
+ path: 'auth/:token',
+ pageBuilder: (_, state) => CustomTransitionPage(
+ child: VerifyMagicLinkScreen(
+ magicLinkToken: state.pathParameters['token']!,
+ ),
+ transitionsBuilder: (_, animation, _, child) {
+ return FadeTransition(opacity: animation, child: child);
+ },
+ ),
+ ),
+ ],
+ ),
+ GoRoute(
+ path: '/verify-mobilepay/:id',
+ pageBuilder: (_, state) => MaterialPage(
+ // TODO(marfavi): Implement MobilePay verification screen
+ child: Container(),
+ ),
+ ),
+ StatefulShellRoute.indexedStack(
+ // fade in the main scaffold (doesn't affect branch transitions)
+ pageBuilder: (_, _, shell) => CustomTransitionPage(
+ child: ScaffoldWithNestedNavigation(navigationShell: shell),
+ transitionsBuilder: (_, animation, _, child) {
+ return FadeTransition(opacity: animation, child: child);
+ },
+ ),
+ branches: [
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/tickets',
+ pageBuilder: (context, state) => const NoTransitionPage(
+ child: TicketsScreen(),
+ ),
+ routes: [
+ GoRoute(
+ path: 'buy',
+ builder: (_, _) => const BuyTicketsScreen(),
+ routes: [
+ GoRoute(
+ path: 'ticket/:id',
+ pageBuilder: (context, state) {
+ // we don't use id here, but in a real app you might
+ // fetch the product details based on the id
+ // we pass the whole product via extra
+ //
+ // cast state.extra to Product
+ final product = state.extra! as Product;
+ return MaterialPage(
+ fullscreenDialog: true,
+ child: TicketDetailScreen(product: product),
+ );
+ },
+ ),
+ ],
+ ),
+ GoRoute(
+ path: 'redeem_voucher',
+ pageBuilder: (context, state) => const MaterialPage(
+ child: RedeemVoucherScreen(),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/receipts',
+ pageBuilder: (context, state) => const NoTransitionPage(
+ child: ReceiptsScreen(),
+ ),
+ routes: [
+ GoRoute(
+ path: 'purchase_receipt/:id',
+ // TODO(marfavi): Implement receipt screen
+ builder: (context, state) => Container(),
+ ),
+ GoRoute(
+ path: 'swipe_receipt/:id',
+ // TODO(marfavi): Implement receipt screen
+ builder: (context, state) => Container(),
+ ),
+ ],
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/stats',
+ pageBuilder: (context, state) =>
+ const NoTransitionPage(child: StatsScreen()),
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/settings',
+ pageBuilder: (context, state) =>
+ const NoTransitionPage(child: SettingsScreen()),
+ routes: [
+ GoRoute(
+ path: 'your-profile',
+ pageBuilder: (context, state) =>
+ const MaterialPage(child: YourProfileScreen()),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ];
+
+ FutureOr redirect(BuildContext context, GoRouterState state) {
+ final loc = state.matchedLocation;
+ final isLoggedIn = context.read().state is AuthAuthenticated;
+
+ // User is going anywhere within [/login, /login/email-sent, /login/auth/]
+ final goingToLoginFlow = loc.startsWith('/login');
+
+ // User is specifically accessing the app via a magic link (/login/auth/)
+ final goingToAuthenticate = loc.startsWith('/login/auth/');
+
+ // User is starting the app
+ final isStartingApp = loc == '/';
+
+ // If not logged in, always go to login unless already going there
+ // (or starting the app, which will handle redirection itself)
+ if (!isLoggedIn &&
+ !goingToLoginFlow &&
+ !goingToAuthenticate &&
+ !isStartingApp) {
+ if (kDebugMode) {
+ print('Redirecting to /login');
+ }
+ return '/login';
+ }
+
+ // If logged in and accessing app via login deep link, redirect to main app
+ if (isLoggedIn && goingToAuthenticate) {
+ // Show a snackbar after the frame is rendered
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('You are already logged in.')),
+ );
+ });
+ return '/tickets';
+ }
+
+ // If logged in and going to login, redirect to main app
+ if (isLoggedIn && goingToLoginFlow) {
+ return '/tickets';
+ }
+
+ // No need to redirect at all
+ return null;
+ }
+
+ FutureOr onEnter(
+ BuildContext context,
+ GoRouterState currentState,
+ GoRouterState nextState,
+ GoRouter goRouter,
+ ) {
+ final currentLoc = currentState.matchedLocation;
+ final nextLoc = nextState.matchedLocation;
+ final isLoggedIn = context.read().state is AuthAuthenticated;
+
+ // User is going anywhere within [/login, /login/email-sent, /login/auth/]
+ final goingToLoginFlow = nextLoc.startsWith('/login');
+
+ // User is starting the app
+ final isStartingApp = nextLoc == '/';
+
+ // We consider the 'main' app sections to be the branches under the shell.
+ final isInMainArea =
+ currentLoc.startsWith('/tickets') ||
+ currentLoc.startsWith('/receipts') ||
+ currentLoc.startsWith('/stats') ||
+ currentLoc.startsWith('/settings');
+
+ // If the user is in the main app area and trying to go to the login flow
+ // while already logged in, block the navigation and show a snackbar.
+ if (isLoggedIn && goingToLoginFlow && isInMainArea) {
+ if (kDebugMode) {
+ print('Navigation to $nextLoc blocked: already logged in.');
+ }
+ return Block.then(
+ () => ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('You are already logged in.')),
+ ),
+ );
+ }
+ // If the user is not logged in and trying to go to the main app area,
+ // block the navigation and show a snackbar.
+ if (!isLoggedIn && !goingToLoginFlow && !isStartingApp) {
+ if (kDebugMode) {
+ print('Navigation to $nextLoc blocked: not logged in.');
+ }
+ return Block.then(
+ () => ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Please log in to continue.')),
+ ),
+ );
+ }
+ return const Allow();
+ }
+}
diff --git a/lib/app/splash_screen.dart b/lib/app/splash_screen.dart
new file mode 100644
index 0000000..e5a70ae
--- /dev/null
+++ b/lib/app/splash_screen.dart
@@ -0,0 +1,19 @@
+import 'package:cafe_analog_app/core/widgets/analog_circular_progress_indicator.dart';
+import 'package:cafe_analog_app/tickets/use_ticket/delayed_fade_in.dart';
+import 'package:flutter/material.dart';
+
+class SplashScreen extends StatelessWidget {
+ const SplashScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Scaffold(
+ body: Center(
+ child: DelayedFadeIn(
+ delay: Duration(seconds: 1),
+ child: AnalogCircularProgressIndicator(spinnerColor: .dark),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/core/loading_overlay.dart b/lib/core/loading_overlay.dart
new file mode 100644
index 0000000..398f79f
--- /dev/null
+++ b/lib/core/loading_overlay.dart
@@ -0,0 +1,23 @@
+import 'dart:async';
+
+import 'package:cafe_analog_app/core/widgets/analog_circular_progress_indicator.dart';
+import 'package:cafe_analog_app/tickets/use_ticket/delayed_fade_in.dart';
+import 'package:flutter/material.dart';
+
+/// Shows a loading overlay dialog.
+///
+/// The dialog can be dismissed by popping the navigator, e.g. `context.pop()`.
+void showLoadingOverlay(BuildContext context) {
+ unawaited(
+ showDialog(
+ context: context,
+ barrierColor: Theme.of(context).colorScheme.surface.withAlpha(225),
+ barrierDismissible: false,
+ builder: (_) => const Center(
+ child: DelayedFadeIn(
+ child: AnalogCircularProgressIndicator(spinnerColor: .dark),
+ ),
+ ),
+ ),
+ );
+}
diff --git a/lib/core/widgets/analog_circular_progress_indicator.dart b/lib/core/widgets/analog_circular_progress_indicator.dart
new file mode 100644
index 0000000..f815500
--- /dev/null
+++ b/lib/core/widgets/analog_circular_progress_indicator.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+
+enum SpinnerColor { light, dark }
+
+class AnalogCircularProgressIndicator extends StatelessWidget {
+ const AnalogCircularProgressIndicator({
+ required this.spinnerColor,
+ super.key,
+ });
+
+ final SpinnerColor spinnerColor;
+
+ @override
+ Widget build(BuildContext context) {
+ return CircularProgressIndicator(
+ semanticsLabel: 'Loading',
+ strokeWidth: 10,
+ strokeCap: .round,
+ color: spinnerColor == .dark
+ ? Theme.of(context).colorScheme.onSurface
+ : Colors.white,
+ );
+ }
+}
diff --git a/lib/core/widgets/form.dart b/lib/core/widgets/form.dart
index 05680df..85b5293 100644
--- a/lib/core/widgets/form.dart
+++ b/lib/core/widgets/form.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
/// The type of validation to apply to the form input.
enum FormInputType { email, nonEmptyString }
@@ -99,7 +100,6 @@ class _AnalogFormState extends State {
children: [
TextFormField(
controller: _controller,
- autofocus: true,
keyboardType: widget.inputType == FormInputType.email
? TextInputType.emailAddress
: TextInputType.text,
@@ -109,7 +109,7 @@ class _AnalogFormState extends State {
decoration: InputDecoration(
labelText: widget.labelText,
filled: true,
- helperText: widget.hintText,
+ helperText: widget.hintText ?? '',
helperMaxLines: 2,
helperStyle: const TextStyle(
fontSize: 12,
@@ -119,6 +119,7 @@ class _AnalogFormState extends State {
errorMaxLines: 2,
),
),
+ const Gap(8),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.secondary,
diff --git a/lib/core/widgets/screen.dart b/lib/core/widgets/screen.dart
index 83f0706..13ec63d 100644
--- a/lib/core/widgets/screen.dart
+++ b/lib/core/widgets/screen.dart
@@ -1,4 +1,4 @@
-import 'package:cafe_analog_app/app.dart';
+import 'package:cafe_analog_app/app/app_brightness_provider.dart';
import 'package:cafe_analog_app/core/widgets/app_bar.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
diff --git a/lib/login/bloc/authentication_cubit.dart b/lib/login/bloc/authentication_cubit.dart
new file mode 100644
index 0000000..f89bc82
--- /dev/null
+++ b/lib/login/bloc/authentication_cubit.dart
@@ -0,0 +1,98 @@
+import 'package:bloc/bloc.dart';
+import 'package:cafe_analog_app/login/data/authentication_token_repository.dart';
+import 'package:cafe_analog_app/login/data/authentication_tokens.dart';
+import 'package:cafe_analog_app/login/data/login_repository.dart';
+import 'package:cafe_analog_app/login/ui/authentication_navigator.dart';
+import 'package:equatable/equatable.dart';
+
+part 'authentication_state.dart';
+
+/// Cubit responsible for managing authentication state.
+///
+/// It handles login, logout, token refresh, and emits appropriate
+/// states based on the authentication status. These states are used by
+/// [AuthNavigator] to navigate the user through the app.
+class AuthCubit extends Cubit {
+ AuthCubit({
+ required AuthTokenRepository authTokenRepository,
+ required LoginRepository loginRepository,
+ }) : _authTokenRepository = authTokenRepository,
+ _loginRepository = loginRepository,
+ super(const AuthInitial());
+
+ final AuthTokenRepository _authTokenRepository;
+ final LoginRepository _loginRepository;
+
+ /// Check current authentication status and emit appropriate state.
+ Future start() async {
+ emit(const AuthLoading());
+ final newState = await _authTokenRepository
+ .getTokens()
+ .match(
+ (couldNotGetTokens) => AuthFailure(reason: couldNotGetTokens.reason),
+ (maybeTokens) => maybeTokens.match(
+ AuthUnauthenticated.new, // on none
+ (tokens) => AuthAuthenticated(tokens: tokens), // on some
+ ),
+ )
+ .run();
+ emit(newState);
+ }
+
+ /// Log the user out and clear stored tokens.
+ Future logOut() async {
+ emit(const AuthLoading());
+ final newState = await _authTokenRepository
+ .clearTokens()
+ .match(
+ (couldNotClear) => AuthFailure(reason: couldNotClear.reason),
+ (_) => const AuthUnauthenticated(),
+ )
+ .run();
+ emit(newState);
+ }
+
+ /// The user has requested a login magic link to be sent.
+ Future sendLoginLink({required String email}) async {
+ emit(const AuthLoading());
+ final newState = await _loginRepository
+ .requestMagicLink(email)
+ .match(
+ (didNotSendLink) => AuthFailure(reason: didNotSendLink.reason),
+ (_) => AuthEmailSent(email: email),
+ )
+ .run();
+ emit(newState);
+ }
+
+ /// Authenticate the user with the token provided from the magic link.
+ Future authenticateWithToken({required String magicLinkToken}) async {
+ emit(const AuthLoading());
+
+ // Exchange the magic link token for auth tokens.
+ final authenticateEither = await _loginRepository
+ .authenticateWithMagicLinkToken(magicLinkToken)
+ .run();
+
+ // If authentication failed, emit failure.
+ // Otherwise save tokens and emit authenticated.
+ final newState = await authenticateEither.match(
+ (didNotAuth) async => AuthFailure(reason: didNotAuth.reason),
+ (tokens) async {
+ final saveEither = await _authTokenRepository.saveTokens(tokens).run();
+ return saveEither.match(
+ (couldNotSave) => AuthFailure(reason: couldNotSave.reason),
+ (savedTokens) => AuthAuthenticated(tokens: savedTokens),
+ );
+ },
+ );
+
+ emit(newState);
+ }
+
+ /// Refresh the JWT token.
+ Future refreshToken({required AuthTokens tokens}) async {
+ // FIXME(marfavi): implement token refresh logic
+ throw UnimplementedError();
+ }
+}
diff --git a/lib/login/bloc/authentication_state.dart b/lib/login/bloc/authentication_state.dart
new file mode 100644
index 0000000..8bcb8bc
--- /dev/null
+++ b/lib/login/bloc/authentication_state.dart
@@ -0,0 +1,52 @@
+part of 'authentication_cubit.dart';
+
+sealed class AuthState extends Equatable {
+ const AuthState();
+
+ @override
+ List