This commit is contained in:
Aditya Pulipaka
2025-07-10 18:52:04 -05:00
commit e0a41761ec
166 changed files with 8444 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

45
.metadata Normal file
View File

@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ea121f8859e4b13e47a8f845e4586164519588bc"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: android
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: ios
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: linux
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: macos
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: web
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: windows
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"cmake.ignoreCMakeListsMissing": true
}

16
README.md Normal file
View File

@@ -0,0 +1,16 @@
# blind_master
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

28
analysis_options.yaml Normal file
View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.blind_master"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.blind_master"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="blind_master"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package com.example.blind_master
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

21
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip

View File

@@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")

BIN
assets/images/2xwhite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/images/EveningSill.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
assets/images/MorningSill.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
assets/images/NightSill.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
assets/images/NoonSill.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
assets/images/sun_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

34
ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

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

View File

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

43
ios/Podfile Normal file
View File

@@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.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

36
ios/Podfile.lock Normal file
View File

@@ -0,0 +1,36 @@
PODS:
- Flutter (1.0.0)
- flutter_blue_plus_darwin (0.0.2):
- Flutter
- FlutterMacOS
- flutter_secure_storage (6.0.0):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_blue_plus_darwin:
:path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
COCOAPODS: 1.16.2

View File

@@ -0,0 +1,743 @@
// !$*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 */; };
37168F2E6E0EB2FD2E2670EA /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A76A4A76AA7A29FE3F7CE6A5 /* Pods_RunnerTests.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D7B7B6794BF4D359B149B767 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D247D8E18BB543B8AF71BF4 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
10395B783492C4BB69EF8AE9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
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>"; };
6D247D8E18BB543B8AF71BF4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97606C416DE8FC726A943531 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
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>"; };
A43606B2521A3B50E340351F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
A76A4A76AA7A29FE3F7CE6A5 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BB7A0C4EE0B5F8D2B88C4F2A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
DEDF765E232BE217F084C791 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
E343CCFF9F5544C52464C63A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D7B7B6794BF4D359B149B767 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
FCA400F977BBF85DE1EE9916 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
37168F2E6E0EB2FD2E2670EA /* Pods_RunnerTests.framework 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 = (
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 */,
FB2136F1EB4879FFCA098756 /* Pods */,
AAE1A6D2DD25E745E39CFD95 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
AAE1A6D2DD25E745E39CFD95 /* Frameworks */ = {
isa = PBXGroup;
children = (
6D247D8E18BB543B8AF71BF4 /* Pods_Runner.framework */,
A76A4A76AA7A29FE3F7CE6A5 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
FB2136F1EB4879FFCA098756 /* Pods */ = {
isa = PBXGroup;
children = (
E343CCFF9F5544C52464C63A /* Pods-Runner.debug.xcconfig */,
BB7A0C4EE0B5F8D2B88C4F2A /* Pods-Runner.release.xcconfig */,
DEDF765E232BE217F084C791 /* Pods-Runner.profile.xcconfig */,
10395B783492C4BB69EF8AE9 /* Pods-RunnerTests.debug.xcconfig */,
A43606B2521A3B50E340351F /* Pods-RunnerTests.release.xcconfig */,
97606C416DE8FC726A943531 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
E1EF6CE5B7A94A9EFFC25DFB /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
FCA400F977BBF85DE1EE9916 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
2666096A969AF9D188332BCA /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
8019B14E73A9D908BAF155A6 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
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 */
2666096A969AF9D188332BCA /* [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;
};
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";
};
8019B14E73A9D908BAF155A6 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
E1EF6CE5B7A94A9EFFC25DFB /* [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 */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_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 = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = YK2DB9NT3S;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster;
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;
baseConfigurationReference = 10395B783492C4BB69EF8AE9 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A43606B2521A3B50E340351F /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 97606C416DE8FC726A943531 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster.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 = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_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 = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = YK2DB9NT3S;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster;
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 = YK2DB9NT3S;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster;
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 */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

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

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@@ -0,0 +1,56 @@
{
"images" : [
{
"filename" : "1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "1xwhite.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "blinds-icon-simple-thin-outline_1223784-4520-removebg-preview 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "2xwhite.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "3xwhite.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_3" orientation="portrait" appearance="dark"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</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">
<rect key="frame" x="0.0" y="0.0" width="402" height="874"/>
<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">
<rect key="frame" x="95" y="353" width="250" height="250"/>
</imageView>
</subviews>
<color key="backgroundColor" name="LaunchBG"/>
<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="80.916030534351137" y="264.08450704225356"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="250" height="250"/>
<namedColor name="LaunchBG">
<color red="0.99999994039535522" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" 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="23721"/>
<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="-17" y="-40"/>
</scene>
</scenes>
</document>

60
ios/Runner/Info.plist Normal file
View File

@@ -0,0 +1,60 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Blind Master</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>blind_master</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>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>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to nearby devices.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app requires Bluetooth access.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSLocalNetworkUsageDescription</key>
<string>This app uses the local network for debugging and development.</string>
</dict>
</plist>

View File

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

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class BlindmasterProgressIndicator extends StatelessWidget {
const BlindmasterProgressIndicator({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Center(
child: CircularProgressIndicator(
color: Theme.of(context).primaryColorDark,
)
)
);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
SnackBar errorSnackbar(
Object e, {
Color backgroundColor = const Color.fromARGB(255, 196, 26, 14),
Duration duration = const Duration(seconds: 3),
}) {
return SnackBar(
backgroundColor: Color.fromARGB(255, 196, 26, 14),
content: Text(
e.toString().replaceFirst(RegExp(r'^[^:]+:\s*'), ''),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Colors.white
)
)
);
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/widgets.dart';
PageRouteBuilder<dynamic> fadeTransition(Widget nextScreen) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => nextScreen,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
transitionDuration: Duration(milliseconds: 500)
);
}

View File

@@ -0,0 +1,113 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:socket_io_client/socket_io_client.dart' as IO;
String local = Platform.isAndroid ? '10.0.2.2' : 'localhost';
String fromDevice = '192.168.1.190';
String host = local;
int port = 3000;
String socketString = "$scheme://$host:$port";
String scheme = 'http';
Future<http.Response?> secureGet(String path, {Map<String, dynamic>? queryParameters}) async{
final storage = FlutterSecureStorage();
final token = await storage.read(key: 'token');
if (token == null) return null;
final uri = Uri(
scheme: scheme,
host: host,
port: port, // your host
path: path, // your path
queryParameters: queryParameters?.map((key, value) => MapEntry(key, value.toString())),
);
return await http
.get(
uri,
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
)
.timeout(const Duration(seconds: 10)); // 🚀 Timeout added
}
Future<http.Response?> securePost(Map<String, dynamic> payload, String path) async{
final storage = FlutterSecureStorage();
final token = await storage.read(key: 'token');
if (token == null) return null;
final uri = Uri(
scheme: scheme,
host: host,
port: port, // your host
path: path, // your path
);
return await http.post(
uri,
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: json.encode(payload),
)
.timeout(const Duration(seconds: 10)); // 🚀 Timeout added
}
Future<http.Response> regularGet(String path) async {
final uri = Uri(
scheme: scheme,
host: host,
port: port, // your host
path: path, // your path
);
return await http.get(
uri,
headers: {
'Content-Type': 'application/json',
}
)
.timeout(const Duration(seconds: 10)); // 🚀 Timeout added
}
Future<http.Response> regularPost(Map<String, dynamic> payload, String path) async{
final uri = Uri(
scheme: scheme,
host: host,
port: port, // your host
path: path, // your path
);
return await http.post(
uri,
headers: {
'Content-Type': 'application/json',
},
body: json.encode(payload)
).timeout(const Duration(seconds: 10)); // 🚀 Timeout added
}
Future<IO.Socket?> connectSocket() async {
final storage = FlutterSecureStorage();
final token = await storage.read(key: 'token');
if (token == null) return null;
final socket = IO.io(
socketString,
IO.OptionBuilder()
.setTransports(['websocket'])
.setAuth({'token': token})
.disableAutoConnect().build(),
);
socket.connect();
return socket;
}

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class BlindMasterMainInput extends StatelessWidget {
const BlindMasterMainInput(this.label, {super.key, this.controller, this.validator, this.color, this.password = false});
final String label;
final TextEditingController? controller;
final Color? color;
final bool password;
final String? Function(String?)? validator;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
child:TextFormField(
validator: validator,
obscureText: password,
enableSuggestions: false,
autocorrect: false,
controller: controller,
style: TextStyle(
color: color
),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
labelText: label,
labelStyle: TextStyle(color: color),
contentPadding: EdgeInsets.all(10),
),
)
);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class TitleText extends StatelessWidget {
const TitleText(this.text, {super.key, this.txtClr});
final String text;
final Color? txtClr;
@override
Widget build(BuildContext context) {
return Text(
text,
style: GoogleFonts.aBeeZee(
color: txtClr,
fontSize: 50
),
textAlign: TextAlign.center,
);
}
}

View File

@@ -0,0 +1,152 @@
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:flutter/material.dart';
import 'package:blind_master/BlindMasterResources/text_inputs.dart';
import 'package:blind_master/BlindMasterResources/title_text.dart';
class CreateUserScreen extends StatefulWidget {
const CreateUserScreen({super.key});
@override
State<CreateUserScreen> createState() => _CreateUserScreenState();
}
class _CreateUserScreenState extends State<CreateUserScreen> {
final emailController = TextEditingController();
final nameController = TextEditingController();
final passwordController = TextEditingController();
final _passFormKey = GlobalKey<FormState>();
final _emailFormKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
}
Future<void> attemptCreate() async{
try {
if (!_emailFormKey.currentState!.validate() || !_passFormKey.currentState!.validate()) {
throw Exception('Invalid information entered!');
}
final payload = {
'name': nameController.text.trim(),
'email': emailController.text.trim(),
'password': passwordController.text,
};
final response = await regularPost(payload, 'create_user');
if (response.statusCode == 201) {
if(mounted) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.green[800],
content: Text(
"Account Successfully Created!",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15),
),
)
);
Navigator.pop(context);
}
} else {
if (response.statusCode == 409) throw Exception('Email Already In Use!');
throw Exception('Create failed: ${response.statusCode}');
}
} catch(e) {
if (!mounted) return;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
errorSnackbar(e)
);
return;
}
}
String? confirmPasswordValidator(String? input) {
if (input == passwordController.text) {
return null;
}
else {
return "Passwords do not match!";
}
}
String? emailValidator(String? input) {
if (input == null || input.isEmpty) {
return 'Email is required';
}
final emailPattern = r'^[^@]+@[^@]+\.[^@]+$';
if (!RegExp(emailPattern).hasMatch(input)) {
return 'Enter a valid email address';
}
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Theme.of(context).primaryColorLight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TitleText("Create Account", txtClr: Colors.white),
Container(
padding: EdgeInsets.fromLTRB(40, 10, 40, 10),
child: Column(
children: [
BlindMasterMainInput("Preferred Name (optional)", controller: nameController),
SizedBox(height: 20),
Form(
key: _emailFormKey,
autovalidateMode: AutovalidateMode.onUnfocus,
child: BlindMasterMainInput(
"Email",
controller: emailController,
validator: emailValidator,
),
),
Form(
key: _passFormKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
BlindMasterMainInput(
"Password",
password: true,
controller: passwordController
),
BlindMasterMainInput(
"Confirm Password",
validator: confirmPasswordValidator,
password: true,
)
],
)
)
],
),
),
ElevatedButton(
onPressed: attemptCreate,
child: Text(
"Create"
),
)
],
),
),
);
}
}

View File

@@ -0,0 +1,126 @@
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/BlindMasterScreens/Startup/create_user_screen.dart';
import 'package:blind_master/BlindMasterScreens/home_screen.dart';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/fade_transition.dart';
import 'package:blind_master/BlindMasterResources/text_inputs.dart';
import 'package:blind_master/BlindMasterResources/title_text.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final emailController = TextEditingController();
final passwordController = TextEditingController();
bool inputWrong = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
// Clean up the controller when the widget is removed from the
// widget tree.
emailController.dispose();
passwordController.dispose();
super.dispose();
}
Future<void> attemptSignIn() async {
final email = emailController.text.trim();
final password = passwordController.text;
final payload = {
'email': email,
'password': password,
}; // query parameters
try {
final response = await regularPost(payload, 'login');
if (response.statusCode != 200) {
if (response.statusCode == 400) {throw Exception('Email and Password Necessary');}
else if (response.statusCode == 500) {throw Exception('Server Error');}
else {throw Exception('Incorrect email or password');}
}
final body = json.decode(response.body) as Map<String, dynamic>;
final token = body['token'] as String;
if (token.isEmpty) throw Exception('Token Not Received');
final storage = FlutterSecureStorage();
await storage.write(key: 'token', value: token);
} catch(e) {
if (!mounted) return;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
errorSnackbar(e)
);
return;
}
if (!mounted) return;
Navigator.pushReplacement(
context,
fadeTransition(HomeScreen()),
);
}
void switchToCreate() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CreateUserScreen()),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Theme.of(context).primaryColorLight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TitleText("BlindMaster"),
Container(
padding: EdgeInsets.fromLTRB(40, 10, 40, 10),
child: Column(
children: [
BlindMasterMainInput("Email", controller: emailController),
BlindMasterMainInput("Password", controller: passwordController, password: true,),
],
),
),
Container(
padding: EdgeInsets.only(bottom:10),
child: ElevatedButton(
onPressed: attemptSignIn,
child: Text(
"Log In"
),
)
),
ElevatedButton(
onPressed: switchToCreate,
child: Text(
"Create Account"
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,89 @@
import 'dart:async';
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/BlindMasterScreens/home_screen.dart';
import 'package:blind_master/BlindMasterScreens/Startup/login_screen.dart';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/fade_transition.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
Widget nextScreen = LoginScreen();
@override
void initState() {
super.initState();
_routeNext();
}
Future<void> _routeNext() async {
await verifyToken();
_animateBackgroundBasedOnTime();
}
Future<void> verifyToken() async{
final storage = FlutterSecureStorage();
try {
http.Response? response = await secureGet('verify');
if (response == null) {
nextScreen = LoginScreen();
return;
}
if (response.statusCode == 200) {
final body = json.decode(response.body) as Map<String, dynamic>;
final newToken = body['token'];
if (newToken != null) {
await storage.write(key: 'token', value: newToken); // ✅ Rotate
}
nextScreen = HomeScreen();
} else {
nextScreen = LoginScreen();
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
errorSnackbar(e)
);
nextScreen = LoginScreen();
}
}
void _animateBackgroundBasedOnTime() {
// Optionally navigate to your main app after a short delay
Timer(const Duration(seconds: 3), () {
Navigator.pushReplacement(
context,
// MaterialPageRoute(builder: (context) => const MainAppScreen())
fadeTransition(nextScreen),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedContainer(
duration: const Duration(milliseconds: 500),
color: Theme.of(context).primaryColorLight,
child: Center(
child: Image.asset('assets/images/2xwhite.png', height: 250)
),
),
);
}
}

View File

@@ -0,0 +1,191 @@
import 'dart:async';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterScreens/addingDevices/device_setup.dart';
import 'package:blind_master/utils_from_FBPExample/extra.dart';
import 'package:blind_master/utils_from_FBPExample/scan_result_tile.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:google_fonts/google_fonts.dart';
class AddDevice extends StatefulWidget {
const AddDevice({super.key});
@override
State<AddDevice> createState() => _AddDeviceState();
}
class _AddDeviceState extends State<AddDevice> {
List<ScanResult> _scanResults = [];
bool _isScanning = false;
late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
late StreamSubscription<bool> _isScanningSubscription;
BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
late StreamSubscription<BluetoothAdapterState> _adapterStateSubscription;
bool _isConnecting = false;
@override
void initState() {
super.initState();
initBluetoothandStartScan();
}
@override
void dispose() {
_scanResultsSubscription.cancel();
_isScanningSubscription.cancel();
_adapterStateSubscription.cancel();
super.dispose();
}
Future<void> _startScan() async {
try {
await FlutterBluePlus.startScan(
timeout: const Duration(seconds: 15),
withServices: [
Guid("181C"),
],
webOptionalServices: [
Guid("181C"), // user input
],
);
} catch (e, backtrace) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
print("backtrace: $backtrace");
}
setState(() {});
}
Future<void> initBluetoothandStartScan() async {
_adapterStateSubscription = FlutterBluePlus.adapterState.listen((state) {
if (mounted) {
setState(() => _adapterState = state);
if (state == BluetoothAdapterState.on) {
_startScan();
}
}
});
_scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
if (mounted) {
setState(() => _scanResults = results);
// setState(() => _scanResults = results.where((r) => r.device.platformName == "BlindMaster").toList());
}
}, onError: (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
errorSnackbar(e)
);
});
_isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
if (mounted) {
setState(() => _isScanning = state);
}
});
}
Future onConnectPressed(BluetoothDevice device) async {
if (_isConnecting) return;
_isConnecting = true;
await device.connectAndUpdateStream().catchError((e) {
if(!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
});
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DeviceSetup(device: device))
).then((_) {
device.disconnectAndUpdateStream().catchError((e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
});
_isConnecting = false;
});
}
Future onRefresh() async {
if (_isScanning == false) {
await _startScan();
}
if (mounted) {
setState(() {});
}
return Future.delayed(Duration(milliseconds: 500));
}
Widget _buildScanResultTiles() {
final res = _scanResults.where((r) => r.advertisementData.advName == "BlindMaster Device" && r.rssi > -55);
return (res.isNotEmpty)
? ListView(
children: [
...res.map((r) => ScanResultTile(result: r, onTap: () => onConnectPressed(r.device)))
],
)
: _isScanning ? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: Theme.of(context).primaryColorLight,
),
SizedBox(
height: 10,
),
Text(
"Nothing Yet...",
)
]
),
)
: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: const Center(
child: Text(
"No BlindMaster devices found nearby",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Add a Device",
style: GoogleFonts.aBeeZee(),
),
backgroundColor: Theme.of(context).primaryColorLight,
),
body: _adapterState != BluetoothAdapterState.on
? SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: const Center(
child: Text(
"Bluetooth is off.\nPlease turn it on to scan for devices.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
)
)
: RefreshIndicator(
onRefresh: onRefresh,
child: _buildScanResultTiles()
),
);
}
}

View File

@@ -0,0 +1,329 @@
import 'dart:async';
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterScreens/addingDevices/set_device_name.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:google_fonts/google_fonts.dart';
class DeviceSetup extends StatefulWidget {
final BluetoothDevice device;
const DeviceSetup({super.key, required this.device});
@override
State<DeviceSetup> createState() => _DeviceSetupState();
}
class _DeviceSetupState extends State<DeviceSetup> {
List<BluetoothService> _services = [];
List<String> openNetworks = [];
List<String> pskNetworks = [];
late StreamSubscription<List<int>> _ssidSub;
StreamSubscription<List<int>>? _confirmSub;
Widget? wifiList;
String? message;
String? password;
final passControl = TextEditingController();
@override void initState() {
super.initState();
initSetup();
}
@override
void dispose() {
_ssidSub.cancel();
_confirmSub?.cancel();
passControl.dispose();
super.dispose();
}
Future setWifiListListener(BluetoothCharacteristic ssidListChar) async {
setState(() {
wifiList = null;
});
await ssidListChar.setNotifyValue(true);
_ssidSub = ssidListChar.onValueReceived.listen((List<int> value) {
List<String> ssidList = [];
bool noNetworks = false;
try {
final val = utf8.decode(value);
if (val == ';') noNetworks = true;
ssidList = val
.split(';')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
openNetworks = ssidList
.where((s) => s.split(',')[1] == "OPEN")
.map((s) => s.split(',')[0])
.toList();
pskNetworks = ssidList
.where((s) => s.split(',')[1] == "SECURED")
.map((s) => s.split(',')[0])
.toList();
} catch (e) {
if(!mounted)return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
setState(() {
wifiList = noNetworks
? SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: const Center(
child: Text(
"No networks found...",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15),
),
),
),
)
: ssidList.isNotEmpty
? ListView(
children: [
...buildSSIDs()
],
)
: null;
});
});
}
List<Widget> buildSSIDs() {
List<Widget> open = openNetworks.map((s) {
return Card(
child: ListTile(
leading: const Icon(Icons.wifi),
title: Text(s),
trailing: const Icon(Icons.arrow_forward_ios_rounded),
onTap: () {
openConnect(s);
},
),
);
}).toList();
List<Widget> secure = pskNetworks.map((s) {
return Card(
child: ListTile(
leading: const Icon(Icons.wifi_password),
title: Text(s),
trailing: const Icon(Icons.arrow_forward_ios_rounded),
onTap: () {
setPassword(s);
},
),
);
}).toList();
return open + secure;
}
Future openConnect(String ssid) async {
await transmitWiFiDetails(ssid, "");
}
Future discoverServices() async{
try {
_services = await widget.device.discoverServices();
} catch (e) {
if(!mounted)return;
ScaffoldMessenger.of(context).showSnackBar(
errorSnackbar(e)
);
return;
}
try {
_services = _services.where((s) => s.uuid.str.toUpperCase() == "181C").toList();
if (_services.length != 1) throw Exception("Invalid Bluetooth Broadcast");
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
return;
}
}
Future initSetup() async {
await discoverServices();
final ssidListChar = _services[0].characteristics.lastWhere((c) => c.uuid.str == "0000");
await setWifiListListener(ssidListChar);
refreshWifiList();
}
Future setPassword(String ssid) async {
String? password = await showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: Text(
ssid,
style: GoogleFonts.aBeeZee(),
),
content: Form(
autovalidateMode: AutovalidateMode.onUnfocus,
child: TextFormField(
controller: passControl,
obscureText: true,
decoration: const InputDecoration(hintText: "Enter password"),
validator: (value) {
if (value == null) return "null input";
if (value.length < 8) return "not long enough!";
return null;
},
textInputAction: TextInputAction.send,
onFieldSubmitted: (value) => Navigator.pop(dialogContext, passControl.text),
),
),
actions: [
TextButton(
onPressed: () {
passControl.clear();
Navigator.pop(dialogContext);
},
child: const Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.pop(dialogContext, passControl.text);
passControl.clear();
},
child: const Text("Connect"),
),
],
);
}
);
await transmitWiFiDetails(ssid, password);
}
Future transmitWiFiDetails(String ssid, String? password) async {
if (password == null) return;
setState(() {
wifiList = null;
message = "Attempting Connection...";
});
final ssidEntryChar = _services[0].characteristics.lastWhere((c) => c.uuid.str == "0001");
try {
try {
await ssidEntryChar.write(utf8.encode(ssid), withoutResponse: ssidEntryChar.properties.writeWithoutResponse);
} catch (e) {
throw Exception("SSID Write Error");
}
} catch (e){
if(!mounted)return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
refreshWifiList();
return;
}
final passEntryChar = _services[0].characteristics.lastWhere((c) => c.uuid.str == "0002");
try {
try {
await passEntryChar.write(utf8.encode(password), withoutResponse: passEntryChar.properties.writeWithoutResponse);
} catch (e) {
throw Exception("Password Write Error");
}
} catch (e){
if(!mounted)return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
refreshWifiList();
return;
}
final connectConfirmChar = _services[0].characteristics.lastWhere((c) => c.uuid.str == "0005");
final tokenEntryChar = _services[0].characteristics.lastWhere((c) => c.uuid.str == "0003");
await connectConfirmChar.setNotifyValue(true);
_confirmSub = connectConfirmChar.onValueReceived.listen((List<int> connectVal) {
try {
final connectResponse = utf8.decode(connectVal);
if (connectResponse == "Connected") {
if (!mounted) return;
_confirmSub?.cancel();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SetDeviceName(tokenEntryChar: tokenEntryChar, device: widget.device),
)
).then((_) {
if (widget.device.isConnected) {
refreshWifiList();
}
});
} else if (connectResponse == "Error") {
_confirmSub?.cancel();
throw Exception("SSID/Password Incorrect");
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
refreshWifiList();
return;
}
});
}
Future refreshWifiList() async{
final ssidRefreshChar = _services[0].characteristics.lastWhere((c) => c.uuid.str == "0004");
setState(() {
message = null;
});
try {
try {
await ssidRefreshChar.write(utf8.encode("refresh"), withoutResponse: ssidRefreshChar.properties.writeWithoutResponse);
} catch (e) {
throw Exception ("Refresh Error");
}
} catch (e) {
if (!mounted)return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Select WiFi Network",
style: GoogleFonts.aBeeZee(),
),
backgroundColor: Theme.of(context).primaryColorLight,
),
body: RefreshIndicator(
onRefresh: refreshWifiList,
child: wifiList ?? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: Theme.of(context).primaryColorLight,
),
SizedBox(
height: 10,
),
Text(
message ?? "Fetching Networks...",
textAlign: TextAlign.center,
)
]
),
)
),
);
}
}

View File

@@ -0,0 +1,128 @@
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/BlindMasterResources/text_inputs.dart';
import 'package:blind_master/BlindMasterScreens/home_screen.dart';
import 'package:blind_master/utils_from_FBPExample/extra.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:google_fonts/google_fonts.dart';
class SetDeviceName extends StatefulWidget {
const SetDeviceName({super.key, required this.tokenEntryChar, required this.device});
final BluetoothCharacteristic tokenEntryChar;
final BluetoothDevice device;
@override
State<SetDeviceName> createState() => _SetDeviceNameState();
}
class _SetDeviceNameState extends State<SetDeviceName> {
final deviceNameController = TextEditingController();
Widget? screen;
@override
void initState() {
initScreen();
super.initState();
}
@override
void dispose() {
deviceNameController.dispose();
super.dispose();
}
void initScreen() {
screen = Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlindMasterMainInput(
"Device Name (Different from others)",
controller: deviceNameController,
),
ElevatedButton(
onPressed: onPressed,
child: Text(
"Add to Account"
)
),
],
);
}
Future addDevice(String name) async {
final payload = {'deviceName': name};
String? token;
try {
final tokenResponse = await securePost(payload, 'add_device');
if (tokenResponse == null) return;
if (tokenResponse.statusCode != 201) {
if (tokenResponse.statusCode == 404) {throw Exception("Somehow the id of your device wasn't found??");}
else if (tokenResponse.statusCode == 409) {throw Exception('Device Name in Use');}
else {throw Exception('Server Error');}
}
final jsonResponse = json.decode(tokenResponse.body) as Map<String, dynamic>;
final fetchedToken = jsonResponse['token'];
if (fetchedToken == null || fetchedToken is! String) {
throw Exception('Invalid token in response');
}
token = fetchedToken;
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
return;
}
try {
try {
await widget.tokenEntryChar.write(utf8.encode(token), withoutResponse: widget.tokenEntryChar.properties.writeWithoutResponse);
} catch (e) {
throw Exception("Token Write Error");
}
} catch (e){
if(!mounted)return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
return;
}
await widget.device.disconnectAndUpdateStream().catchError((e) {
if(!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
});
if (!mounted) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
(route) => false,
);
}
Future onPressed() async {
setState(() {
screen = null;
});
await(addDevice(deviceNameController.text));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Name Your Device",
style: GoogleFonts.aBeeZee(),
),
backgroundColor: Theme.of(context).primaryColorLight,
),
body: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: Center(
child: screen ?? CircularProgressIndicator(color: Theme.of(context).primaryColorLight),
)
)
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class GroupsMenu extends StatefulWidget {
const GroupsMenu({super.key});
@override
State<GroupsMenu> createState() => _GroupsMenuState();
}
class _GroupsMenuState extends State<GroupsMenu> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@@ -0,0 +1,75 @@
import 'package:blind_master/BlindMasterScreens/groupControl/groups_menu.dart';
import 'package:blind_master/BlindMasterScreens/individualControl/devices_menu.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int currentPageIndex = 0;
String greeting = "";
@override
void initState() {
super.initState();
getGreeting();
}
void getGreeting() {
final hour = DateTime.now().hour;
if (hour >= 5 && hour < 12) {
greeting = "Good Morning!";
} else if (hour >= 12 && hour < 18) {
greeting = "Good Afternoon!";
} else if (hour >= 18 && hour < 22) {
greeting = "Good Evening!";
} else {greeting = "😴";}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).primaryColorLight,
centerTitle: false,
title: Text(
greeting,
style: GoogleFonts.aBeeZee(),
),
foregroundColor: Colors.white,
),
bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) {
setState(() {
currentPageIndex = index;
});
},
indicatorColor: Theme.of(context).primaryColorDark,
selectedIndex: currentPageIndex,
destinations: const <Widget>[
NavigationDestination(
selectedIcon: Icon(Icons.blinds_rounded),
icon: Icon(Icons.blinds_closed_rounded),
label: 'Devices',
),
NavigationDestination(
icon: Icon(Icons.window_outlined),
selectedIcon: Icon(Icons.window_rounded),
label: 'Groups',
),
],
),
body:
<Widget>[
DevicesMenu(),
GroupsMenu(),
][currentPageIndex],
);
}
}

View File

@@ -0,0 +1,406 @@
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/BlindMasterResources/text_inputs.dart';
import 'package:blind_master/BlindMasterScreens/individualControl/peripheral_screen.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class DeviceScreen extends StatefulWidget {
final int deviceId;
const DeviceScreen({super.key, required this.deviceId});
@override
State<DeviceScreen> createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
bool enabled = false;
final _newPeripheralNameController = TextEditingController();
final _hubRenameController = TextEditingController();
List<Map<String, dynamic>> peripherals = [];
List occports = [];
Widget? peripheralList;
String deviceName = "...";
@override
void initState() {
super.initState();
initAll();
}
Future initAll() async {
await getDeviceName();
await populatePeripherals();
}
Future getDeviceName() async {
try {
final payload = {
"deviceId": widget.deviceId
};
final response = await secureGet('device_name', queryParameters: payload);
if (response == null) throw Exception("no response!");
if (response.statusCode == 200) {
final body = json.decode(response.body) as Map<String, dynamic>;
setState(() {
deviceName = body['device_name'];
});
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future populatePeripherals() async {
setState(() {
peripheralList = null;
});
try {
final payload = {
"deviceId": widget.deviceId
};
final response = await secureGet('peripheral_list', queryParameters: payload);
if (response == null) throw Exception("no response!");
if (response.statusCode == 200) {
final body = json.decode(response.body) as Map<String, dynamic>;
final names = body['peripheral_names'] as List;
final ids = body['peripheral_ids'] as List;
occports = body['port_nums'] as List;
peripherals = List.generate(names.length, (i) => {
'id': ids[i],
'name': names[i],
'port': occports[i]
});
peripherals.sort((a, b) => (a['port'] as int).compareTo(b['port'] as int));
enabled = peripherals.length < 4;
}
setState(() {
peripheralList = RefreshIndicator(
onRefresh: populatePeripherals,
child: peripherals.isEmpty ? SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: const Center(
child: Text(
"No peripherals found...\nAdd one using the '+' button",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
),
),
) : ListView.builder(
itemCount: peripherals.length,
itemBuilder: (context, i) {
final peripheral = peripherals[i];
return Dismissible(
key: Key(peripheral['id'].toString()),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: const Icon(Icons.delete, color: Colors.white),
),
confirmDismiss: (direction) async {
// Ask for confirmation (optional)
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Peripheral'),
content: const Text('Are you sure you want to delete this peripheral?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Delete'),
),
],
),
);
},
onDismissed: (direction) => deletePeripheral(peripheral['id'], i),
child: Card(
child: ListTile(
leading: const Icon(Icons.blinds),
title: Text(peripheral['name']),
subtitle: Text("Port #${peripheral['port']}"),
trailing: const Icon(Icons.arrow_forward_ios_rounded),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PeripheralScreen(peripheralId: peripheral['id'],
peripheralNum: peripheral['port'], deviceId: widget.deviceId,),
),
).then((_) { populatePeripherals(); });
},
),
),
);
},
),
);
});
return Future.delayed(Duration(milliseconds: 500));
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future deletePeripheral(int id, int i) async {
setState(() {
peripherals.removeAt(i);
peripheralList = null;
});
final payload = {
'periphId': id,
};
try {
final response = await securePost(payload, 'delete_peripheral');
if (response == null) return;
if (response.statusCode != 204) {
if (response.statusCode == 404) {throw Exception('Device Not Found');}
else if (response.statusCode == 500) {throw Exception('Server Error');}
}
if (mounted){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text(
'Deleted',
textAlign: TextAlign.center,
)
),
);
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
populatePeripherals();
}
void addPeripheral() {
var freePorts = <int>{};
for (int i = 1; i < 5; i++) {
freePorts.add(i);
}
freePorts = freePorts.difference(occports.toSet());
int? port = freePorts.firstOrNull;
showDialog(
context: context,
builder: (BuildContext dialogContext) { // Use dialogContext for navigation within the dialog
return AlertDialog(
title: Text(
'New Peripheral',
style: GoogleFonts.aBeeZee(),
),
content: Column(
mainAxisSize: MainAxisSize.min, // Keep column compact
children: <Widget>[
TextFormField(
controller: _newPeripheralNameController,
decoration: const InputDecoration(
labelText: 'Peripheral Name',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
DropdownButtonFormField<int>(
value: port,
decoration: const InputDecoration(
labelText: 'Hub Port',
border: OutlineInputBorder(),
),
items: freePorts.map((int number) {
return DropdownMenuItem<int>(
value: number,
child: Text('$number'),
);
}).toList(),
onChanged: (int? newValue) {
setState(() {
port = newValue;
});
},
),
],
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(dialogContext).pop();
},
child: const Text(
"Cancel",
style: TextStyle(
color: Colors.red
),
)
),
ElevatedButton(
onPressed: () {
uploadPeriphData(_newPeripheralNameController.text, port);
Navigator.of(dialogContext).pop();
},
child: const Text("Add"),
),
]
)
],
);
}
);
}
Future uploadPeriphData(String name, int? port) async {
try {
if (name.isEmpty || port == null) {
throw Exception("Name and Port Required");
}
final payload = {
'device_id': widget.deviceId,
'port_num': port,
'peripheral_name': name
};
final response = await securePost(payload, 'add_peripheral');
if (response == null) throw Exception("Auth Error");
if (response.statusCode != 201) {
if (response.statusCode == 409) throw Exception("Choose a unique name!");
throw Exception("Server Error");
}
populatePeripherals();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
void rename() {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text(
"Rename Hub",
style: GoogleFonts.aBeeZee(),
),
content: BlindMasterMainInput("New Hub Name", controller: _hubRenameController,),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(dialogContext).pop();
},
child: const Text(
"Cancel",
style: TextStyle(
color: Colors.red
),
)
),
ElevatedButton(
onPressed: () {
updateHubName(_hubRenameController.text, widget.deviceId);
Navigator.of(dialogContext).pop();
},
child: const Text("Confirm")
)
],
)
],
);
}
);
}
Future updateHubName(String name, int id) async {
try {
if (name.isEmpty) throw Exception("New name cannot be empty!");
final payload = {
'deviceId': id,
'newName': name,
};
final response = await securePost(payload, 'rename_device');
if (response == null) throw Exception("Auth Error");
if (response.statusCode != 204) {
if (response.statusCode == 409) throw Exception("Choose a unique name!");
throw Exception("Server Error");
}
getDeviceName();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
deviceName,
style: GoogleFonts.aBeeZee(),
),
backgroundColor: Theme.of(context).primaryColorLight,
foregroundColor: Colors.white,
),
body: peripheralList ?? SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Center(
child: CircularProgressIndicator(
color: Theme.of(context).primaryColorLight,
),
)
),
floatingActionButton: Container(
padding: EdgeInsets.all(25),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FloatingActionButton(
backgroundColor: Theme.of(context).primaryColorDark,
foregroundColor: Theme.of(context).highlightColor,
heroTag: "rename",
onPressed: rename,
tooltip: "Rename Hub",
child: Icon(Icons.drive_file_rename_outline_sharp),
),
FloatingActionButton(
backgroundColor: enabled
? Theme.of(context).primaryColorDark
: Theme.of(context).disabledColor,
foregroundColor: Theme.of(context).highlightColor,
heroTag: "add",
onPressed: enabled ? addPeripheral : null,
tooltip: "Add Peripheral",
child: Icon(Icons.add),
)
],
)
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}

View File

@@ -0,0 +1,175 @@
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/BlindMasterScreens/addingDevices/add_device.dart';
import 'package:blind_master/BlindMasterScreens/individualControl/device_screen.dart';
import 'package:flutter/material.dart';
class DevicesMenu extends StatefulWidget {
const DevicesMenu({super.key});
@override
State<DevicesMenu> createState() => _DevicesMenuState();
}
class _DevicesMenuState extends State<DevicesMenu> {
List<Map<String, dynamic>> devices = [];
Widget? deviceList;
@override
void initState() {
super.initState();
getDevices();
}
Future getDevices() async {
try{
final response = await secureGet('device_list');
if (response == null) throw Exception("no response!");
if (response.statusCode == 200) {
final body = json.decode(response.body) as Map<String, dynamic>;
final names = body['devices'] as List;
final ids = body['device_ids'] as List;
devices = List.generate(names.length, (i) => {
'id': ids[i],
'name': names[i],
});
}
} catch(e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
errorSnackbar(e)
);
}
setState(() {
deviceList = RefreshIndicator(
onRefresh: getDevices,
child: devices.isEmpty
? SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: const Center(
child: Text(
"No hubs found...\nAdd one using the '+' button",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
),
),
)
: ListView.builder(
itemCount: devices.length,
itemBuilder: (context, i) {
final device = devices[i];
return Dismissible(
key: Key(device['id'].toString()),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: const Icon(Icons.delete, color: Colors.white),
),
confirmDismiss: (direction) async {
// Ask for confirmation (optional)
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Hub'),
content: const Text('Are you sure you want to delete this hub?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Delete'),
),
],
),
);
},
onDismissed: (direction) {
// Actually delete the device
deleteDevice(device['id'], i);
},
child: Card(
child: ListTile(
leading: const Icon(Icons.blinds),
title: Text(device['name']),
trailing: const Icon(Icons.arrow_forward_ios_rounded),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DeviceScreen(deviceId: device['id']),
),
).then((_) { getDevices(); });
},
),
),
);
},
),
);
});
return Future.delayed(Duration(milliseconds: 500));
}
Future deleteDevice(int id, int i) async {
setState(() {
devices.removeAt(i);
deviceList = null;
});
print("deleting");
final payload = {
'deviceId': id,
};
try {
final response = await securePost(payload, 'delete_device');
if (response == null) return;
if (response.statusCode != 204) {
if (response.statusCode == 404) {throw Exception('Device Not Found');}
else if (response.statusCode == 500) {throw Exception('Server Error');}
}
if (mounted){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Deleted',
textAlign: TextAlign.center,
)
),
);
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
getDevices();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: deviceList ?? const Center(child: CircularProgressIndicator()),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddDevice()),
);
},
foregroundColor: Theme.of(context).highlightColor,
backgroundColor: Theme.of(context).primaryColorDark,
child: Icon(Icons.add),
),
);
}
}

View File

@@ -0,0 +1,524 @@
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/blindmaster_progress_indicator.dart';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/BlindMasterResources/text_inputs.dart';
import 'package:blind_master/BlindMasterScreens/schedules_screen.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
class PeripheralScreen extends StatefulWidget {
const PeripheralScreen({super.key, required this.peripheralId, required this.deviceId, required this.peripheralNum});
final int peripheralId;
final int peripheralNum;
final int deviceId;
@override
State<PeripheralScreen> createState() => _PeripheralScreenState();
}
class _PeripheralScreenState extends State<PeripheralScreen> {
IO.Socket? socket;
String imagePath = "";
String peripheralName = "...";
bool loaded = false;
bool calibrated = false;
bool calibrating = false;
double _blindPosition = 5.0;
DateTime? lastSet;
String lastSetMessage = "";
final _peripheralRenameController = TextEditingController();
void getImage() {
final hour = DateTime.now().hour;
if (hour >= 5 && hour < 10) {
imagePath = 'assets/images/MorningSill.png';
} else if (hour >= 10 && hour < 18) {
imagePath = 'assets/images/NoonSill.png';
} else if (hour >= 18 && hour < 22) {
imagePath = 'assets/images/EveningSill.png';
} else {
imagePath = 'assets/images/NightSill.png';
}
}
@override
void initState() {
super.initState();
initAll();
initSocket();
}
@override
void dispose() {
socket?.disconnect();
socket?.dispose();
super.dispose();
}
Future<void> initSocket() async {
try {
socket = await connectSocket();
if (socket == null) throw Exception("Unsuccessful socket connection");
socket?.on("success", (_) {
socket?.on("posUpdates", (list) {
for (var update in list) {
if (update is Map<String, dynamic>) {
if (update['periphID'] == widget.peripheralId) {
if (!mounted) return;
setState(() {
_blindPosition = (update['pos'] as int).toDouble();
});
}
}
}
});
socket?.on("calib", (periphData) {
if (periphData is Map<String, dynamic>) {
if (periphData['periphID'] == widget.peripheralId) {
if (!mounted) return;
setState(() {
calibrating = true;
calibrated = false;
});
}
}
});
socket?.on("calib_done", (periphData) {
if (periphData is Map<String, dynamic>) {
if (periphData['periphID'] == widget.peripheralId) {
if (!mounted) return;
setState(() {
calibrating = false;
calibrated = true;
});
}
}
});
});
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future<void> calibrate() async {
try {
final payload = {
'periphId': widget.peripheralId
};
final response = await securePost(payload, 'calib');
if (response == null) throw Exception("auth error");
if (response.statusCode != 202) throw Exception("Server Error");
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future<void> cancelCalib() async {
try {
final payload = {
'periphId': widget.peripheralId
};
final response = await securePost(payload, 'cancel_calib');
if (response == null) throw Exception("auth error");
if (response.statusCode != 202) throw Exception("Server Error");
setState(() {
calibrated = false;
calibrating = false;
});
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future<void> getName() async {
try {
final payload = {
'periphId': widget.peripheralId
};
final response = await secureGet('peripheral_name', queryParameters: payload);
if (response == null) throw Exception("auth error");
if (response.statusCode != 200) throw Exception("Server Error");
final body = json.decode(response.body);
setState(() => peripheralName = body['name']);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future loop() async{
try {
final payload = {
'periphId': widget.peripheralId
};
final response = await secureGet('peripheral_status', queryParameters: payload);
if (response == null) throw Exception("auth error");
if (response.statusCode != 200) {
if (response.statusCode == 404) throw Exception("Device Not Found");
throw Exception("Server Error");
}
final body = json.decode(response.body) as Map<String, dynamic>;
if (!body['await_calib']){
if (!body['calibrated']) {
calibrated = false;
calibrating = false;
}
else {
getImage();
final nowUtc = DateTime.now().toUtc();
final lastSetUtc = DateTime.parse(body['last_set']);
final Duration difference = nowUtc.difference(lastSetUtc);
if (!lastSetUtc.isUtc) throw Exception("Why isn't the server giving UTC?");
final diffDays = difference.inDays > 0;
final diffHours = difference.inHours > 0;
final diffMins = difference.inMinutes > 0;
lastSetMessage = "Last set ${diffDays ? '${difference.inDays.toString()} days' : diffHours ? '${difference.inHours.toString()} hours' : diffMins ? '${difference.inMinutes.toString()} minutes' : '${difference.inSeconds.toString()} seconds'} ago";
_blindPosition = (body['last_pos'] as int).toDouble();
calibrated = true;
calibrating = false;
}
}
else {
calibrating = true;
}
setState(() {loaded = true;});
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
Future initAll() async{
getName();
loop();
}
void rename() {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text(
"Rename Peripheral",
style: GoogleFonts.aBeeZee(),
),
content: BlindMasterMainInput("New Peripheral Name", controller: _peripheralRenameController,),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(dialogContext).pop();
},
child: const Text(
"Cancel",
style: TextStyle(
color: Colors.red
),
)
),
ElevatedButton(
onPressed: () {
updatePeriphName(_peripheralRenameController.text, widget.peripheralId);
Navigator.of(dialogContext).pop();
},
child: const Text("Confirm")
)
],
)
],
);
}
);
}
Future updatePeriphName(String name, int id) async {
try {
if (name.isEmpty) throw Exception("New name cannot be empty!");
final payload = {
'periphId': id,
'newName': name,
};
final response = await securePost(payload, 'rename_peripheral');
if (response == null) throw Exception("Auth Error");
if (response.statusCode != 204) {
if (response.statusCode == 409) throw Exception("Choose a unique name!");
throw Exception("Server Error");
}
getName();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
void recalibrate() {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text(
"Recalibrate Peripheral",
style: GoogleFonts.aBeeZee(),
),
content: const Text(
"This will take under a minute",
textAlign: TextAlign.center,
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(dialogContext).pop();
},
child: const Text(
"Cancel",
style: TextStyle(
color: Colors.red
),
)
),
ElevatedButton(
onPressed: () {
calibrate();
Navigator.of(dialogContext).pop();
},
child: const Text("Confirm")
)
],
)
],
);
}
);
}
Future updateBlindPosition() async {
try {
final payload = {
'periphId': widget.peripheralId,
'periphNum': widget.peripheralNum,
'deviceId': widget.deviceId,
'newPos': _blindPosition.toInt(),
};
final response = await securePost(payload, 'manual_position_update');
if (response == null) throw Exception("Auth Error");
if (response.statusCode != 202) {
throw Exception("Server Error");
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
peripheralName,
style: GoogleFonts.aBeeZee(),
),
backgroundColor: Theme.of(context).primaryColorLight,
foregroundColor: Colors.white,
),
body: loaded
? (calibrating
? RefreshIndicator(
onRefresh: initAll,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(20),
child: Text(
"Calibrating... Check again soon."
),
),
ElevatedButton(
onPressed: cancelCalib,
child: const Text(
"Cancel",
style: TextStyle(
color: Colors.red
),
)
)
]
)
)
)
)
)
: (calibrated
? Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
child: Container(
padding: EdgeInsets.fromLTRB(0, 20, 0, 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.15,
),
Stack(
children: [
// Background image
Align(
alignment: Alignment.center,
child: Image.asset(
imagePath,
// fit: BoxFit.cover,
width: MediaQuery.of(context).size.width * 0.7,
),
),
Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05),
height: MediaQuery.of(context).size.width * 0.68,
width: MediaQuery.of(context).size.width * 0.7,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(10, (index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: _blindPosition < 5 ?
5.4 * (5 - _blindPosition)
: 5.4 * (_blindPosition - 5),
width: MediaQuery.of(context).size.width * 0.65, // example
color: const Color.fromARGB(255, 121, 85, 72),
);
}),
),
)
)
],
),
// Slider on the side
Expanded(
child: Center(
child: RotatedBox(
quarterTurns: -1,
child: Slider(
value: _blindPosition,
activeColor: Theme.of(context).primaryColorDark,
thumbColor: Theme.of(context).primaryColorLight,
inactiveColor: Theme.of(context).primaryColorDark,
min: 0,
max: 10,
divisions: 10,
onChanged: (value) {
setState(() {
_blindPosition = value;
updateBlindPosition();
});
},
),
),
)
)
],
),
)
),
Container(
padding: EdgeInsets.all(25),
child: Text(
lastSetMessage
),
),
Container(
padding: EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SchedulesScreen()
)
);
},
child: Text(
"Set Schedules"
)
),
)
]
)
: SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(20),
child: Text(
"Peripheral Not Calibrated"
),
),
ElevatedButton(
onPressed: calibrate,
child: const Text("Calibrate")
)
],
)
)
)))
: BlindmasterProgressIndicator(),
floatingActionButton: Container(
padding: EdgeInsets.all(25),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FloatingActionButton(
heroTag: "rename",
tooltip: "Rename Peripheral",
onPressed: rename,
foregroundColor: Theme.of(context).highlightColor,
backgroundColor: Theme.of(context).primaryColorDark,
child: Icon(Icons.drive_file_rename_outline_sharp),
),
FloatingActionButton(
heroTag: "recalibrate",
tooltip: "Recalibrate Peripheral",
onPressed: recalibrate,
foregroundColor: Theme.of(context).highlightColor,
backgroundColor: Theme.of(context).primaryColorDark,
child: Icon(Icons.swap_vert),
),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class SchedulesScreen extends StatefulWidget {
const SchedulesScreen({super.key});
@override
State<SchedulesScreen> createState() => _SchedulesScreenState();
}
class _SchedulesScreenState extends State<SchedulesScreen> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

65
lib/main.dart Normal file
View File

@@ -0,0 +1,65 @@
import 'package:blind_master/BlindMasterScreens/Startup/splash_screen.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Map<String, Color> getBackgroundBasedOnTime() {
final hour = DateTime.now().hour;
Color secondaryLight;
Color primary;
Color secondaryDark;
if (hour >= 5 && hour < 10) {
// Morning
primary = Colors.orange;
secondaryLight = const Color.fromARGB(255, 255, 204, 128);
secondaryDark = const Color.fromARGB(255, 174, 104, 0);
} else if (hour >= 10 && hour < 18) {
// Afternoon
primary = Colors.blue;
secondaryLight = const Color.fromARGB(255, 144, 202, 249);
secondaryDark = const Color.fromARGB(255, 0, 92, 168);
} else {
// Evening/Night
primary = const Color.fromARGB(255, 71, 17, 137);
secondaryLight = const Color.fromARGB(255, 186, 130, 255);
secondaryDark = const Color.fromARGB(255, 40, 0, 89);
}
return {
'primary': primary,
'secondaryLight': secondaryLight,
'secondaryDark': secondaryDark,
};
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final colors = getBackgroundBasedOnTime();
return MaterialApp(
home: SplashScreen(),
theme: ThemeData(
useMaterial3: true,
primaryColorLight: colors['primary'],
highlightColor: Colors.black,
disabledColor: Colors.grey,
primaryColorDark: colors['secondaryLight'],
brightness: Brightness.light
),
darkTheme: ThemeData(
useMaterial3: true,
highlightColor: Colors.white,
primaryColorLight: colors['primary'],
disabledColor: Colors.grey[800],
primaryColorDark: colors['secondaryDark'],
brightness: Brightness.dark
),
);
}
}

View File

@@ -0,0 +1,51 @@
import 'utils.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
final Map<DeviceIdentifier, StreamControllerReemit<bool>> _cglobal = {};
final Map<DeviceIdentifier, StreamControllerReemit<bool>> _dglobal = {};
/// connect & disconnect + update stream
extension Extra on BluetoothDevice {
// convenience
StreamControllerReemit<bool> get _cstream {
_cglobal[remoteId] ??= StreamControllerReemit(initialValue: false);
return _cglobal[remoteId]!;
}
// convenience
StreamControllerReemit<bool> get _dstream {
_dglobal[remoteId] ??= StreamControllerReemit(initialValue: false);
return _dglobal[remoteId]!;
}
// get stream
Stream<bool> get isConnecting {
return _cstream.stream;
}
// get stream
Stream<bool> get isDisconnecting {
return _dstream.stream;
}
// connect & update stream
Future<void> connectAndUpdateStream() async {
_cstream.add(true);
try {
await connect(mtu: null);
} finally {
_cstream.add(false);
}
}
// disconnect & update stream
Future<void> disconnectAndUpdateStream({bool queue = true}) async {
_dstream.add(true);
try {
await disconnect(queue: queue);
} finally {
_dstream.add(false);
}
}
}

View File

@@ -0,0 +1,64 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
class ScanResultTile extends StatefulWidget {
const ScanResultTile({super.key, required this.result, this.onTap});
final ScanResult result;
final VoidCallback? onTap;
@override
State<ScanResultTile> createState() => _ScanResultTileState();
}
class _ScanResultTileState extends State<ScanResultTile> {
BluetoothConnectionState _connectionState = BluetoothConnectionState.disconnected;
late StreamSubscription<BluetoothConnectionState> _connectionStateSubscription;
@override
void initState() {
super.initState();
_connectionStateSubscription = widget.result.device.connectionState.listen((state) {
_connectionState = state;
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_connectionStateSubscription.cancel();
super.dispose();
}
bool get isConnected {
return _connectionState == BluetoothConnectionState.connected;
}
Widget _buildTitle(BuildContext context) {
return Text(
widget.result.advertisementData.advName,
overflow: TextOverflow.ellipsis,
);
}
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: _buildTitle(context),
subtitle: Text(
"Signal Strength (Less = farther): ${widget.result.rssi}",
overflow: TextOverflow.ellipsis,
),
trailing: Icon(Icons.arrow_forward_ios_rounded),
onTap: widget.result.advertisementData.connectable ? widget.onTap : null,
)
);
}
}

View File

@@ -0,0 +1,153 @@
import 'dart:async';
// It is essentially a stream but:
// 1. we cache the latestValue of the stream
// 2. the "latestValue" is re-emitted whenever the stream is listened to
class StreamControllerReemit<T> {
T? _latestValue;
final StreamController<T> _controller = StreamController<T>.broadcast();
StreamControllerReemit({T? initialValue}) : _latestValue = initialValue;
Stream<T> get stream {
return _latestValue != null ? _controller.stream.newStreamWithInitialValue(_latestValue as T) : _controller.stream;
}
T? get value => _latestValue;
void add(T newValue) {
_latestValue = newValue;
_controller.add(newValue);
}
Future<void> close() {
return _controller.close();
}
}
// return a new stream that immediately emits an initial value
extension _StreamNewStreamWithInitialValue<T> on Stream<T> {
Stream<T> newStreamWithInitialValue(T initialValue) {
return transform(_NewStreamWithInitialValueTransformer(initialValue));
}
}
// Helper for 'newStreamWithInitialValue' method for streams.
class _NewStreamWithInitialValueTransformer<T> extends StreamTransformerBase<T, T> {
/// the initial value to push to the new stream
final T initialValue;
/// controller for the new stream
late StreamController<T> controller;
/// subscription to the original stream
late StreamSubscription<T> subscription;
/// new stream listener count
var listenerCount = 0;
_NewStreamWithInitialValueTransformer(this.initialValue);
@override
Stream<T> bind(Stream<T> stream) {
if (stream.isBroadcast) {
return _bind(stream, broadcast: true);
} else {
return _bind(stream);
}
}
Stream<T> _bind(Stream<T> stream, {bool broadcast = false}) {
/////////////////////////////////////////
/// Original Stream Subscription Callbacks
///
/// When the original stream emits data, forward it to our new stream
void onData(T data) {
controller.add(data);
}
/// When the original stream is done, close our new stream
void onDone() {
controller.close();
}
/// When the original stream has an error, forward it to our new stream
void onError(Object error) {
controller.addError(error);
}
/// When a client listens to our new stream, emit the
/// initial value and subscribe to original stream if needed
void onListen() {
// Emit the initial value to our new stream
controller.add(initialValue);
// listen to the original stream, if needed
if (listenerCount == 0) {
subscription = stream.listen(
onData,
onError: onError,
onDone: onDone,
);
}
// count listeners of the new stream
listenerCount++;
}
//////////////////////////////////////
/// New Stream Controller Callbacks
///
/// (Single Subscription Only) When a client pauses
/// the new stream, pause the original stream
void onPause() {
subscription.pause();
}
/// (Single Subscription Only) When a client resumes
/// the new stream, resume the original stream
void onResume() {
subscription.resume();
}
/// Called when a client cancels their
/// subscription to the new stream,
void onCancel() {
// count listeners of the new stream
listenerCount--;
// when there are no more listeners of the new stream,
// cancel the subscription to the original stream,
// and close the new stream controller
if (listenerCount == 0) {
subscription.cancel();
controller.close();
}
}
//////////////////////////////////////
/// Return New Stream
///
// create a new stream controller
if (broadcast) {
controller = StreamController<T>.broadcast(
onListen: onListen,
onCancel: onCancel,
);
} else {
controller = StreamController<T>(
onListen: onListen,
onPause: onPause,
onResume: onResume,
onCancel: onCancel,
);
}
return controller.stream;
}
}

1
linux/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
flutter/ephemeral

128
linux/CMakeLists.txt Normal file
View File

@@ -0,0 +1,128 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "blind_master")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.blind_master")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

Some files were not shown because too many files have changed in this diff Show More