Initial commit: Fixes for linting and compilation
43
.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
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
@@ -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: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: android
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: ios
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: linux
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: macos
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: web
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: windows
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
|
||||
# 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'
|
||||
127
README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Christian Period Tracker
|
||||
|
||||
A faith-centered period and fertility tracking app for Christian women and their husbands.
|
||||
|
||||
## Features
|
||||
|
||||
### Wife's App (Primary)
|
||||
|
||||
- **Cycle Tracking** - Period logging, predictions, and phase identification
|
||||
- **Symptom Logging** - Mood, energy, cramps, and more
|
||||
- **Daily Devotionals** - Phase-specific scripture and reflections
|
||||
- **NFP Support** - BBT, cervical mucus tracking (for married users)
|
||||
- **Calendar View** - Color-coded cycle visualization
|
||||
- **Private Data** - All data stored locally on device
|
||||
|
||||
### Husband's Companion App
|
||||
|
||||
- **Cycle Overview** - See where she is in her cycle
|
||||
- **Support Tips** - Phase-specific ways to help
|
||||
- **Scripture** - Verses for loving husbands
|
||||
- **Learning Library** - Understanding her cycle, biblical manhood
|
||||
|
||||
## Marital Status Awareness
|
||||
|
||||
The app adapts content based on relationship status:
|
||||
|
||||
- **Single/Engaged**: Health tracking only, no intimacy/fertility content
|
||||
- **Married**: Full features including NFP, fertility window, partner sharing
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: Flutter 3.24+
|
||||
- **State Management**: Riverpod
|
||||
- **Local Database**: Hive (privacy-first, no cloud required)
|
||||
- **UI**: Material 3 + Custom Design System
|
||||
- **Fonts**: Outfit (UI), Lora (Scripture)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Flutter SDK 3.24+
|
||||
- Dart 3.5+
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
cd christian_period_tracker
|
||||
|
||||
# Install dependencies
|
||||
flutter pub get
|
||||
|
||||
# Generate Hive adapters (if needed)
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# Run the app
|
||||
flutter run
|
||||
```
|
||||
|
||||
### Platforms
|
||||
|
||||
- ✅ iOS (iPhone + iPad)
|
||||
- ✅ Android (Phone + Tablet)
|
||||
- ✅ Web (Progressive Web App)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart # App entry point
|
||||
├── theme/
|
||||
│ └── app_theme.dart # Color palette, typography
|
||||
├── models/
|
||||
│ ├── user_profile.dart # User & relationship status
|
||||
│ ├── cycle_entry.dart # Daily tracking data
|
||||
│ └── scripture.dart # Scripture database
|
||||
├── screens/
|
||||
│ ├── splash_screen.dart # Animated splash
|
||||
│ ├── onboarding/ # Welcome, name, status, cycle setup
|
||||
│ ├── home/ # Dashboard with cycle ring
|
||||
│ ├── calendar/ # Month view with phase markers
|
||||
│ ├── log/ # Symptom & period logging
|
||||
│ ├── devotional/ # Daily scripture & reflection
|
||||
│ └── husband/ # Husband companion screens
|
||||
└── widgets/
|
||||
├── cycle_ring.dart # Animated progress ring
|
||||
├── scripture_card.dart # Phase-colored verse display
|
||||
├── quick_log_buttons.dart # Quick action buttons
|
||||
└── tip_card.dart # Contextual tips
|
||||
```
|
||||
|
||||
## Color Palettes
|
||||
|
||||
### Wife's App
|
||||
|
||||
| Color | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| Blush Pink | `#F8E1E7` | Backgrounds, accents |
|
||||
| Rose | `#E8A0B0` | Secondary actions |
|
||||
| Sage Green | `#A8C5A8` | Primary, CTAs |
|
||||
| Lavender | `#D4C4E8` | Tertiary, calm states |
|
||||
| Cream | `#FDF8F5` | Scaffold background |
|
||||
|
||||
### Husband's App
|
||||
|
||||
| Color | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| Navy Blue | `#2C3E50` | Primary |
|
||||
| Steel Blue | `#5D7B93` | Secondary |
|
||||
| Warm Cream | `#F5F0E8` | Background |
|
||||
| Gold | `#C9A961` | Accents, scripture |
|
||||
|
||||
## Privacy
|
||||
|
||||
- **Local-first**: All data stored on device using Hive
|
||||
- **No account required**: Works fully offline
|
||||
- **Optional cloud sync**: Future feature with end-to-end encryption
|
||||
- **Biometric lock**: Planned for sensitive data
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please read the contribution guidelines first.
|
||||
28
analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
13
android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
44
android/app/build.gradle
Normal 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.faithapps.christian_period_tracker"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.faithapps.christian_period_tracker"
|
||||
// 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.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
7
android/app/src/debug/AndroidManifest.xml
Normal 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>
|
||||
45
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="christian_period_tracker"
|
||||
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>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.faithapps.christian_period_tracker
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal 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>
|
||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal 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>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal 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>
|
||||
18
android/app/src/main/res/values/styles.xml
Normal 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>
|
||||
7
android/app/src/profile/AndroidManifest.xml
Normal 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>
|
||||
18
android/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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.3-all.zip
|
||||
25
android/settings.gradle
Normal file
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return 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.1.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
34
ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
1
ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
1
ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
616
ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,616 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
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 */,
|
||||
);
|
||||
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>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
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 */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 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;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 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_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;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
98
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@@ -0,0 +1,98 @@
|
||||
<?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"
|
||||
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"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
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>
|
||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
13
ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
49
ios/Runner/Info.plist
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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>Christian Period Tracker</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>christian_period_tracker</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/>
|
||||
</dict>
|
||||
</plist>
|
||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
49
lib/main.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'theme/app_theme.dart';
|
||||
import 'screens/splash_screen.dart';
|
||||
import 'models/user_profile.dart';
|
||||
import 'models/cycle_entry.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize Hive for local storage
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Register Hive adapters
|
||||
Hive.registerAdapter(UserProfileAdapter());
|
||||
Hive.registerAdapter(CycleEntryAdapter());
|
||||
Hive.registerAdapter(RelationshipStatusAdapter());
|
||||
Hive.registerAdapter(FertilityGoalAdapter());
|
||||
Hive.registerAdapter(MoodLevelAdapter());
|
||||
Hive.registerAdapter(FlowIntensityAdapter());
|
||||
Hive.registerAdapter(CervicalMucusTypeAdapter());
|
||||
Hive.registerAdapter(CyclePhaseAdapter());
|
||||
Hive.registerAdapter(UserRoleAdapter());
|
||||
|
||||
// Open boxes
|
||||
await Hive.openBox<UserProfile>('user_profile');
|
||||
await Hive.openBox<CycleEntry>('cycle_entries');
|
||||
|
||||
runApp(const ProviderScope(child: ChristianPeriodTrackerApp()));
|
||||
}
|
||||
|
||||
class ChristianPeriodTrackerApp extends StatelessWidget {
|
||||
const ChristianPeriodTrackerApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Christian Period Tracker',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.lightTheme,
|
||||
darkTheme: AppTheme.darkTheme,
|
||||
themeMode: ThemeMode.system,
|
||||
home: const SplashScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
335
lib/models/cycle_entry.dart
Normal file
@@ -0,0 +1,335 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'cycle_entry.g.dart';
|
||||
|
||||
/// Mood levels for tracking
|
||||
@HiveType(typeId: 3)
|
||||
enum MoodLevel {
|
||||
@HiveField(0)
|
||||
verySad,
|
||||
|
||||
@HiveField(1)
|
||||
sad,
|
||||
|
||||
@HiveField(2)
|
||||
neutral,
|
||||
|
||||
@HiveField(3)
|
||||
happy,
|
||||
|
||||
@HiveField(4)
|
||||
veryHappy,
|
||||
}
|
||||
|
||||
/// Flow intensity for period days
|
||||
@HiveType(typeId: 4)
|
||||
enum FlowIntensity {
|
||||
@HiveField(0)
|
||||
spotting,
|
||||
|
||||
@HiveField(1)
|
||||
light,
|
||||
|
||||
@HiveField(2)
|
||||
medium,
|
||||
|
||||
@HiveField(3)
|
||||
heavy,
|
||||
}
|
||||
|
||||
/// Cervical mucus type for NFP tracking
|
||||
@HiveType(typeId: 5)
|
||||
enum CervicalMucusType {
|
||||
@HiveField(0)
|
||||
dry,
|
||||
|
||||
@HiveField(1)
|
||||
sticky,
|
||||
|
||||
@HiveField(2)
|
||||
creamy,
|
||||
|
||||
@HiveField(3)
|
||||
eggWhite,
|
||||
|
||||
@HiveField(4)
|
||||
watery,
|
||||
}
|
||||
|
||||
/// Cycle phase
|
||||
@HiveType(typeId: 6)
|
||||
enum CyclePhase {
|
||||
@HiveField(0)
|
||||
menstrual,
|
||||
|
||||
@HiveField(1)
|
||||
follicular,
|
||||
|
||||
@HiveField(2)
|
||||
ovulation,
|
||||
|
||||
@HiveField(3)
|
||||
luteal,
|
||||
}
|
||||
|
||||
/// Daily cycle entry
|
||||
@HiveType(typeId: 7)
|
||||
class CycleEntry extends HiveObject {
|
||||
@HiveField(0)
|
||||
String id;
|
||||
|
||||
@HiveField(1)
|
||||
DateTime date;
|
||||
|
||||
@HiveField(2)
|
||||
bool isPeriodDay;
|
||||
|
||||
@HiveField(3)
|
||||
FlowIntensity? flowIntensity;
|
||||
|
||||
@HiveField(4)
|
||||
MoodLevel? mood;
|
||||
|
||||
@HiveField(5)
|
||||
int? energyLevel; // 1-5
|
||||
|
||||
@HiveField(6)
|
||||
int? crampIntensity; // 1-5
|
||||
|
||||
@HiveField(7)
|
||||
bool hasHeadache;
|
||||
|
||||
@HiveField(8)
|
||||
bool hasBloating;
|
||||
|
||||
@HiveField(9)
|
||||
bool hasBreastTenderness;
|
||||
|
||||
@HiveField(10)
|
||||
bool hasFatigue;
|
||||
|
||||
@HiveField(11)
|
||||
bool hasAcne;
|
||||
|
||||
@HiveField(12)
|
||||
double? basalBodyTemperature; // in Fahrenheit
|
||||
|
||||
@HiveField(13)
|
||||
CervicalMucusType? cervicalMucus;
|
||||
|
||||
@HiveField(14)
|
||||
bool? ovulationTestPositive;
|
||||
|
||||
@HiveField(15)
|
||||
String? notes;
|
||||
|
||||
@HiveField(16)
|
||||
int? sleepHours;
|
||||
|
||||
@HiveField(17)
|
||||
int? waterIntake; // glasses
|
||||
|
||||
@HiveField(18)
|
||||
bool hadExercise;
|
||||
|
||||
@HiveField(19)
|
||||
bool hadIntimacy; // For married users only
|
||||
|
||||
@HiveField(20)
|
||||
DateTime createdAt;
|
||||
|
||||
@HiveField(21)
|
||||
DateTime updatedAt;
|
||||
|
||||
CycleEntry({
|
||||
required this.id,
|
||||
required this.date,
|
||||
this.isPeriodDay = false,
|
||||
this.flowIntensity,
|
||||
this.mood,
|
||||
this.energyLevel,
|
||||
this.crampIntensity,
|
||||
this.hasHeadache = false,
|
||||
this.hasBloating = false,
|
||||
this.hasBreastTenderness = false,
|
||||
this.hasFatigue = false,
|
||||
this.hasAcne = false,
|
||||
this.basalBodyTemperature,
|
||||
this.cervicalMucus,
|
||||
this.ovulationTestPositive,
|
||||
this.notes,
|
||||
this.sleepHours,
|
||||
this.waterIntake,
|
||||
this.hadExercise = false,
|
||||
this.hadIntimacy = false,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
/// Check if any symptoms are logged
|
||||
bool get hasSymptoms =>
|
||||
hasHeadache ||
|
||||
hasBloating ||
|
||||
hasBreastTenderness ||
|
||||
hasFatigue ||
|
||||
hasAcne ||
|
||||
(crampIntensity != null && crampIntensity! > 0);
|
||||
|
||||
/// Check if NFP data is logged
|
||||
bool get hasNFPData =>
|
||||
basalBodyTemperature != null ||
|
||||
cervicalMucus != null ||
|
||||
ovulationTestPositive != null;
|
||||
|
||||
/// Get symptom count
|
||||
int get symptomCount {
|
||||
int count = 0;
|
||||
if (hasHeadache) count++;
|
||||
if (hasBloating) count++;
|
||||
if (hasBreastTenderness) count++;
|
||||
if (hasFatigue) count++;
|
||||
if (hasAcne) count++;
|
||||
if (crampIntensity != null && crampIntensity! > 0) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
/// Copy with updated fields
|
||||
CycleEntry copyWith({
|
||||
String? id,
|
||||
DateTime? date,
|
||||
bool? isPeriodDay,
|
||||
FlowIntensity? flowIntensity,
|
||||
MoodLevel? mood,
|
||||
int? energyLevel,
|
||||
int? crampIntensity,
|
||||
bool? hasHeadache,
|
||||
bool? hasBloating,
|
||||
bool? hasBreastTenderness,
|
||||
bool? hasFatigue,
|
||||
bool? hasAcne,
|
||||
double? basalBodyTemperature,
|
||||
CervicalMucusType? cervicalMucus,
|
||||
bool? ovulationTestPositive,
|
||||
String? notes,
|
||||
int? sleepHours,
|
||||
int? waterIntake,
|
||||
bool? hadExercise,
|
||||
bool? hadIntimacy,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return CycleEntry(
|
||||
id: id ?? this.id,
|
||||
date: date ?? this.date,
|
||||
isPeriodDay: isPeriodDay ?? this.isPeriodDay,
|
||||
flowIntensity: flowIntensity ?? this.flowIntensity,
|
||||
mood: mood ?? this.mood,
|
||||
energyLevel: energyLevel ?? this.energyLevel,
|
||||
crampIntensity: crampIntensity ?? this.crampIntensity,
|
||||
hasHeadache: hasHeadache ?? this.hasHeadache,
|
||||
hasBloating: hasBloating ?? this.hasBloating,
|
||||
hasBreastTenderness: hasBreastTenderness ?? this.hasBreastTenderness,
|
||||
hasFatigue: hasFatigue ?? this.hasFatigue,
|
||||
hasAcne: hasAcne ?? this.hasAcne,
|
||||
basalBodyTemperature: basalBodyTemperature ?? this.basalBodyTemperature,
|
||||
cervicalMucus: cervicalMucus ?? this.cervicalMucus,
|
||||
ovulationTestPositive: ovulationTestPositive ?? this.ovulationTestPositive,
|
||||
notes: notes ?? this.notes,
|
||||
sleepHours: sleepHours ?? this.sleepHours,
|
||||
waterIntake: waterIntake ?? this.waterIntake,
|
||||
hadExercise: hadExercise ?? this.hadExercise,
|
||||
hadIntimacy: hadIntimacy ?? this.hadIntimacy,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension to get display string for enums
|
||||
extension MoodLevelExtension on MoodLevel {
|
||||
String get emoji {
|
||||
switch (this) {
|
||||
case MoodLevel.verySad:
|
||||
return '😢';
|
||||
case MoodLevel.sad:
|
||||
return '😕';
|
||||
case MoodLevel.neutral:
|
||||
return '😐';
|
||||
case MoodLevel.happy:
|
||||
return '🙂';
|
||||
case MoodLevel.veryHappy:
|
||||
return '😄';
|
||||
}
|
||||
}
|
||||
|
||||
String get label {
|
||||
switch (this) {
|
||||
case MoodLevel.verySad:
|
||||
return 'Very Sad';
|
||||
case MoodLevel.sad:
|
||||
return 'Sad';
|
||||
case MoodLevel.neutral:
|
||||
return 'Neutral';
|
||||
case MoodLevel.happy:
|
||||
return 'Happy';
|
||||
case MoodLevel.veryHappy:
|
||||
return 'Very Happy';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FlowIntensityExtension on FlowIntensity {
|
||||
String get label {
|
||||
switch (this) {
|
||||
case FlowIntensity.spotting:
|
||||
return 'Spotting';
|
||||
case FlowIntensity.light:
|
||||
return 'Light';
|
||||
case FlowIntensity.medium:
|
||||
return 'Medium';
|
||||
case FlowIntensity.heavy:
|
||||
return 'Heavy';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CyclePhaseExtension on CyclePhase {
|
||||
String get label {
|
||||
switch (this) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'Menstrual';
|
||||
case CyclePhase.follicular:
|
||||
return 'Follicular';
|
||||
case CyclePhase.ovulation:
|
||||
return 'Ovulation';
|
||||
case CyclePhase.luteal:
|
||||
return 'Luteal';
|
||||
}
|
||||
}
|
||||
|
||||
String get emoji {
|
||||
switch (this) {
|
||||
case CyclePhase.menstrual:
|
||||
return '🩸';
|
||||
case CyclePhase.follicular:
|
||||
return '🌱';
|
||||
case CyclePhase.ovulation:
|
||||
return '🌸';
|
||||
case CyclePhase.luteal:
|
||||
return '🌙';
|
||||
}
|
||||
}
|
||||
|
||||
String get description {
|
||||
switch (this) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'A time for rest and reflection';
|
||||
case CyclePhase.follicular:
|
||||
return 'A time of renewal and energy';
|
||||
case CyclePhase.ovulation:
|
||||
return 'Peak fertility window';
|
||||
case CyclePhase.luteal:
|
||||
return 'A time for patience and self-care';
|
||||
}
|
||||
}
|
||||
}
|
||||
310
lib/models/cycle_entry.g.dart
Normal file
@@ -0,0 +1,310 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'cycle_entry.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class CycleEntryAdapter extends TypeAdapter<CycleEntry> {
|
||||
@override
|
||||
final int typeId = 7;
|
||||
|
||||
@override
|
||||
CycleEntry read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return CycleEntry(
|
||||
id: fields[0] as String,
|
||||
date: fields[1] as DateTime,
|
||||
isPeriodDay: fields[2] as bool,
|
||||
flowIntensity: fields[3] as FlowIntensity?,
|
||||
mood: fields[4] as MoodLevel?,
|
||||
energyLevel: fields[5] as int?,
|
||||
crampIntensity: fields[6] as int?,
|
||||
hasHeadache: fields[7] as bool,
|
||||
hasBloating: fields[8] as bool,
|
||||
hasBreastTenderness: fields[9] as bool,
|
||||
hasFatigue: fields[10] as bool,
|
||||
hasAcne: fields[11] as bool,
|
||||
basalBodyTemperature: fields[12] as double?,
|
||||
cervicalMucus: fields[13] as CervicalMucusType?,
|
||||
ovulationTestPositive: fields[14] as bool?,
|
||||
notes: fields[15] as String?,
|
||||
sleepHours: fields[16] as int?,
|
||||
waterIntake: fields[17] as int?,
|
||||
hadExercise: fields[18] as bool,
|
||||
hadIntimacy: fields[19] as bool,
|
||||
createdAt: fields[20] as DateTime,
|
||||
updatedAt: fields[21] as DateTime,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, CycleEntry obj) {
|
||||
writer
|
||||
..writeByte(22)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.date)
|
||||
..writeByte(2)
|
||||
..write(obj.isPeriodDay)
|
||||
..writeByte(3)
|
||||
..write(obj.flowIntensity)
|
||||
..writeByte(4)
|
||||
..write(obj.mood)
|
||||
..writeByte(5)
|
||||
..write(obj.energyLevel)
|
||||
..writeByte(6)
|
||||
..write(obj.crampIntensity)
|
||||
..writeByte(7)
|
||||
..write(obj.hasHeadache)
|
||||
..writeByte(8)
|
||||
..write(obj.hasBloating)
|
||||
..writeByte(9)
|
||||
..write(obj.hasBreastTenderness)
|
||||
..writeByte(10)
|
||||
..write(obj.hasFatigue)
|
||||
..writeByte(11)
|
||||
..write(obj.hasAcne)
|
||||
..writeByte(12)
|
||||
..write(obj.basalBodyTemperature)
|
||||
..writeByte(13)
|
||||
..write(obj.cervicalMucus)
|
||||
..writeByte(14)
|
||||
..write(obj.ovulationTestPositive)
|
||||
..writeByte(15)
|
||||
..write(obj.notes)
|
||||
..writeByte(16)
|
||||
..write(obj.sleepHours)
|
||||
..writeByte(17)
|
||||
..write(obj.waterIntake)
|
||||
..writeByte(18)
|
||||
..write(obj.hadExercise)
|
||||
..writeByte(19)
|
||||
..write(obj.hadIntimacy)
|
||||
..writeByte(20)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(21)
|
||||
..write(obj.updatedAt);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CycleEntryAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class MoodLevelAdapter extends TypeAdapter<MoodLevel> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
MoodLevel read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return MoodLevel.verySad;
|
||||
case 1:
|
||||
return MoodLevel.sad;
|
||||
case 2:
|
||||
return MoodLevel.neutral;
|
||||
case 3:
|
||||
return MoodLevel.happy;
|
||||
case 4:
|
||||
return MoodLevel.veryHappy;
|
||||
default:
|
||||
return MoodLevel.verySad;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, MoodLevel obj) {
|
||||
switch (obj) {
|
||||
case MoodLevel.verySad:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case MoodLevel.sad:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case MoodLevel.neutral:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case MoodLevel.happy:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case MoodLevel.veryHappy:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MoodLevelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class FlowIntensityAdapter extends TypeAdapter<FlowIntensity> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
FlowIntensity read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return FlowIntensity.spotting;
|
||||
case 1:
|
||||
return FlowIntensity.light;
|
||||
case 2:
|
||||
return FlowIntensity.medium;
|
||||
case 3:
|
||||
return FlowIntensity.heavy;
|
||||
default:
|
||||
return FlowIntensity.spotting;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, FlowIntensity obj) {
|
||||
switch (obj) {
|
||||
case FlowIntensity.spotting:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case FlowIntensity.light:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case FlowIntensity.medium:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case FlowIntensity.heavy:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FlowIntensityAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class CervicalMucusTypeAdapter extends TypeAdapter<CervicalMucusType> {
|
||||
@override
|
||||
final int typeId = 5;
|
||||
|
||||
@override
|
||||
CervicalMucusType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return CervicalMucusType.dry;
|
||||
case 1:
|
||||
return CervicalMucusType.sticky;
|
||||
case 2:
|
||||
return CervicalMucusType.creamy;
|
||||
case 3:
|
||||
return CervicalMucusType.eggWhite;
|
||||
case 4:
|
||||
return CervicalMucusType.watery;
|
||||
default:
|
||||
return CervicalMucusType.dry;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, CervicalMucusType obj) {
|
||||
switch (obj) {
|
||||
case CervicalMucusType.dry:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case CervicalMucusType.sticky:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case CervicalMucusType.creamy:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case CervicalMucusType.eggWhite:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case CervicalMucusType.watery:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CervicalMucusTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class CyclePhaseAdapter extends TypeAdapter<CyclePhase> {
|
||||
@override
|
||||
final int typeId = 6;
|
||||
|
||||
@override
|
||||
CyclePhase read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return CyclePhase.menstrual;
|
||||
case 1:
|
||||
return CyclePhase.follicular;
|
||||
case 2:
|
||||
return CyclePhase.ovulation;
|
||||
case 3:
|
||||
return CyclePhase.luteal;
|
||||
default:
|
||||
return CyclePhase.menstrual;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, CyclePhase obj) {
|
||||
switch (obj) {
|
||||
case CyclePhase.menstrual:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case CyclePhase.follicular:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case CyclePhase.ovulation:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case CyclePhase.luteal:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CyclePhaseAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
235
lib/models/scripture.dart
Normal file
@@ -0,0 +1,235 @@
|
||||
/// Scripture model for daily verses and devotionals
|
||||
class Scripture {
|
||||
final String verse;
|
||||
final String reference;
|
||||
final String? reflection;
|
||||
final List<String> applicablePhases;
|
||||
final List<String> applicableContexts;
|
||||
|
||||
const Scripture({
|
||||
required this.verse,
|
||||
required this.reference,
|
||||
this.reflection,
|
||||
this.applicablePhases = const [],
|
||||
this.applicableContexts = const [],
|
||||
});
|
||||
}
|
||||
|
||||
/// Pre-defined scriptures for the app
|
||||
class ScriptureDatabase {
|
||||
/// Scriptures for menstrual phase (rest, comfort)
|
||||
static const List<Scripture> menstrualScriptures = [
|
||||
Scripture(
|
||||
verse: "Come to me, all you who are weary and burdened, and I will give you rest.",
|
||||
reference: "Matthew 11:28",
|
||||
reflection: "Your body is doing important work. Rest is not weakness—it's wisdom.",
|
||||
applicablePhases: ['menstrual'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "He gives strength to the weary and increases the power of the weak.",
|
||||
reference: "Isaiah 40:29",
|
||||
applicablePhases: ['menstrual'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "The Lord is my shepherd; I shall not want. He makes me lie down in green pastures.",
|
||||
reference: "Psalm 23:1-2",
|
||||
applicablePhases: ['menstrual'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "Be still, and know that I am God.",
|
||||
reference: "Psalm 46:10",
|
||||
reflection: "Use this time of slowing down to be present with God.",
|
||||
applicablePhases: ['menstrual'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "My grace is sufficient for you, for my power is made perfect in weakness.",
|
||||
reference: "2 Corinthians 12:9",
|
||||
applicablePhases: ['menstrual'],
|
||||
),
|
||||
];
|
||||
|
||||
/// Scriptures for follicular phase (renewal, strength)
|
||||
static const List<Scripture> follicularScriptures = [
|
||||
Scripture(
|
||||
verse: "She is clothed with strength and dignity; she can laugh at the days to come.",
|
||||
reference: "Proverbs 31:25",
|
||||
reflection: "You're entering a season of renewed energy. Use it for His glory.",
|
||||
applicablePhases: ['follicular'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "I can do all this through him who gives me strength.",
|
||||
reference: "Philippians 4:13",
|
||||
applicablePhases: ['follicular'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "But those who hope in the Lord will renew their strength. They will soar on wings like eagles.",
|
||||
reference: "Isaiah 40:31",
|
||||
applicablePhases: ['follicular'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "This is the day the Lord has made; let us rejoice and be glad in it.",
|
||||
reference: "Psalm 118:24",
|
||||
applicablePhases: ['follicular'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "The Lord your God is with you, the Mighty Warrior who saves.",
|
||||
reference: "Zephaniah 3:17",
|
||||
applicablePhases: ['follicular'],
|
||||
),
|
||||
];
|
||||
|
||||
/// Scriptures for ovulation phase (creation, beauty)
|
||||
static const List<Scripture> ovulationScriptures = [
|
||||
Scripture(
|
||||
verse: "For you created my inmost being; you knit me together in my mother's womb. I praise you because I am fearfully and wonderfully made.",
|
||||
reference: "Psalm 139:13-14",
|
||||
reflection: "Your body reflects the incredible creativity of God.",
|
||||
applicablePhases: ['ovulation'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "Children are a heritage from the Lord, offspring a reward from him.",
|
||||
reference: "Psalm 127:3",
|
||||
applicablePhases: ['ovulation'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "See, I am doing a new thing! Now it springs up; do you not perceive it?",
|
||||
reference: "Isaiah 43:19",
|
||||
applicablePhases: ['ovulation'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "Every good and perfect gift is from above.",
|
||||
reference: "James 1:17",
|
||||
applicablePhases: ['ovulation'],
|
||||
),
|
||||
];
|
||||
|
||||
/// Scriptures for luteal phase / TWW (patience, trust)
|
||||
static const List<Scripture> lutealScriptures = [
|
||||
Scripture(
|
||||
verse: "For I know the plans I have for you, declares the Lord, plans to prosper you and not to harm you, plans to give you hope and a future.",
|
||||
reference: "Jeremiah 29:11",
|
||||
reflection: "Whatever this season holds, God's plans for you are good.",
|
||||
applicablePhases: ['luteal'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "Do not be anxious about anything, but in every situation, by prayer and petition, with thanksgiving, present your requests to God.",
|
||||
reference: "Philippians 4:6",
|
||||
applicablePhases: ['luteal'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "Trust in the Lord with all your heart and lean not on your own understanding.",
|
||||
reference: "Proverbs 3:5",
|
||||
applicablePhases: ['luteal'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "The Lord is close to the brokenhearted and saves those who are crushed in spirit.",
|
||||
reference: "Psalm 34:18",
|
||||
applicablePhases: ['luteal'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "And the peace of God, which transcends all understanding, will guard your hearts and your minds in Christ Jesus.",
|
||||
reference: "Philippians 4:7",
|
||||
applicablePhases: ['luteal'],
|
||||
),
|
||||
Scripture(
|
||||
verse: "Wait for the Lord; be strong and take heart and wait for the Lord.",
|
||||
reference: "Psalm 27:14",
|
||||
applicablePhases: ['luteal'],
|
||||
),
|
||||
];
|
||||
|
||||
/// Scriptures for husbands
|
||||
static const List<Scripture> husbandScriptures = [
|
||||
Scripture(
|
||||
verse: "Husbands, love your wives, just as Christ loved the church and gave himself up for her.",
|
||||
reference: "Ephesians 5:25",
|
||||
reflection: "Love sacrificially—putting her needs before your own.",
|
||||
),
|
||||
Scripture(
|
||||
verse: "Husbands, in the same way be considerate as you live with your wives, and treat them with respect.",
|
||||
reference: "1 Peter 3:7",
|
||||
),
|
||||
Scripture(
|
||||
verse: "Two are better than one, because they have a good return for their labor.",
|
||||
reference: "Ecclesiastes 4:9",
|
||||
),
|
||||
Scripture(
|
||||
verse: "Be completely humble and gentle; be patient, bearing with one another in love.",
|
||||
reference: "Ephesians 4:2",
|
||||
),
|
||||
Scripture(
|
||||
verse: "Above all, love each other deeply, because love covers over a multitude of sins.",
|
||||
reference: "1 Peter 4:8",
|
||||
),
|
||||
Scripture(
|
||||
verse: "A husband should fulfill his duty to his wife.",
|
||||
reference: "1 Corinthians 7:3",
|
||||
),
|
||||
Scripture(
|
||||
verse: "He who finds a wife finds what is good and receives favor from the Lord.",
|
||||
reference: "Proverbs 18:22",
|
||||
),
|
||||
];
|
||||
|
||||
/// General womanhood scriptures
|
||||
static const List<Scripture> womanhoodScriptures = [
|
||||
Scripture(
|
||||
verse: "Charm is deceptive, and beauty is fleeting; but a woman who fears the Lord is to be praised.",
|
||||
reference: "Proverbs 31:30",
|
||||
),
|
||||
Scripture(
|
||||
verse: "She opens her mouth with wisdom, and the teaching of kindness is on her tongue.",
|
||||
reference: "Proverbs 31:26",
|
||||
),
|
||||
Scripture(
|
||||
verse: "Your beauty should not come from outward adornment... Rather, it should be that of your inner self, the unfading beauty of a gentle and quiet spirit.",
|
||||
reference: "1 Peter 3:3-4",
|
||||
),
|
||||
Scripture(
|
||||
verse: "God is within her, she will not fall; God will help her at break of day.",
|
||||
reference: "Psalm 46:5",
|
||||
),
|
||||
];
|
||||
|
||||
/// Get scripture for current phase
|
||||
static Scripture getScriptureForPhase(String phase) {
|
||||
final List<Scripture> scriptures;
|
||||
switch (phase.toLowerCase()) {
|
||||
case 'menstrual':
|
||||
scriptures = menstrualScriptures;
|
||||
break;
|
||||
case 'follicular':
|
||||
scriptures = follicularScriptures;
|
||||
break;
|
||||
case 'ovulation':
|
||||
scriptures = ovulationScriptures;
|
||||
break;
|
||||
case 'luteal':
|
||||
scriptures = lutealScriptures;
|
||||
break;
|
||||
default:
|
||||
scriptures = [...menstrualScriptures, ...follicularScriptures, ...ovulationScriptures, ...lutealScriptures];
|
||||
}
|
||||
|
||||
// Return a scripture based on the day of year for variety
|
||||
final dayOfYear = DateTime.now().difference(DateTime(DateTime.now().year, 1, 1)).inDays;
|
||||
return scriptures[dayOfYear % scriptures.length];
|
||||
}
|
||||
|
||||
/// Get scripture for husband
|
||||
static Scripture getHusbandScripture() {
|
||||
final dayOfYear = DateTime.now().difference(DateTime(DateTime.now().year, 1, 1)).inDays;
|
||||
return husbandScriptures[dayOfYear % husbandScriptures.length];
|
||||
}
|
||||
|
||||
/// Get all scriptures
|
||||
static List<Scripture> getAllScriptures() {
|
||||
return [
|
||||
...menstrualScriptures,
|
||||
...follicularScriptures,
|
||||
...ovulationScriptures,
|
||||
...lutealScriptures,
|
||||
...womanhoodScriptures,
|
||||
];
|
||||
}
|
||||
}
|
||||
163
lib/models/user_profile.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'user_profile.g.dart';
|
||||
|
||||
/// User's relationship status
|
||||
@HiveType(typeId: 0)
|
||||
enum RelationshipStatus {
|
||||
@HiveField(0)
|
||||
single,
|
||||
|
||||
@HiveField(1)
|
||||
engaged,
|
||||
|
||||
@HiveField(2)
|
||||
married,
|
||||
}
|
||||
|
||||
/// Fertility tracking goal for married users
|
||||
@HiveType(typeId: 1)
|
||||
enum FertilityGoal {
|
||||
@HiveField(0)
|
||||
tryingToConceive, // TTC
|
||||
|
||||
@HiveField(1)
|
||||
tryingToAvoid, // TTA - NFP
|
||||
|
||||
@HiveField(2)
|
||||
justTracking,
|
||||
}
|
||||
|
||||
/// User profile model
|
||||
@HiveType(typeId: 2)
|
||||
class UserProfile extends HiveObject {
|
||||
@HiveField(0)
|
||||
String id;
|
||||
|
||||
@HiveField(1)
|
||||
String name;
|
||||
|
||||
@HiveField(2)
|
||||
RelationshipStatus relationshipStatus;
|
||||
|
||||
@HiveField(3)
|
||||
FertilityGoal? fertilityGoal;
|
||||
|
||||
@HiveField(4)
|
||||
int averageCycleLength;
|
||||
|
||||
@HiveField(5)
|
||||
int averagePeriodLength;
|
||||
|
||||
@HiveField(6)
|
||||
DateTime? lastPeriodStartDate;
|
||||
|
||||
@HiveField(7)
|
||||
bool notificationsEnabled;
|
||||
|
||||
@HiveField(8)
|
||||
String? devotionalTime; // HH:mm format
|
||||
|
||||
@HiveField(9)
|
||||
bool hasCompletedOnboarding;
|
||||
|
||||
@HiveField(10)
|
||||
DateTime createdAt;
|
||||
|
||||
@HiveField(11)
|
||||
DateTime updatedAt;
|
||||
|
||||
@HiveField(12)
|
||||
String? partnerName; // For married users
|
||||
|
||||
@HiveField(14, defaultValue: UserRole.wife)
|
||||
UserRole role;
|
||||
|
||||
@HiveField(15, defaultValue: false)
|
||||
bool isIrregularCycle;
|
||||
|
||||
UserProfile({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.relationshipStatus = RelationshipStatus.single,
|
||||
this.fertilityGoal,
|
||||
this.averageCycleLength = 28,
|
||||
this.averagePeriodLength = 5,
|
||||
this.lastPeriodStartDate,
|
||||
this.notificationsEnabled = true,
|
||||
this.devotionalTime,
|
||||
this.hasCompletedOnboarding = false,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.partnerName,
|
||||
|
||||
this.role = UserRole.wife,
|
||||
this.isIrregularCycle = false,
|
||||
});
|
||||
|
||||
/// Check if user is married
|
||||
bool get isMarried => relationshipStatus == RelationshipStatus.married;
|
||||
|
||||
/// Check if user is trying to conceive
|
||||
bool get isTTC => fertilityGoal == FertilityGoal.tryingToConceive;
|
||||
|
||||
/// Check if user is practicing NFP
|
||||
bool get isNFP => fertilityGoal == FertilityGoal.tryingToAvoid;
|
||||
|
||||
/// Check if user is husband
|
||||
bool get isHusband => role == UserRole.husband;
|
||||
|
||||
/// Should show fertility content
|
||||
bool get showFertilityContent =>
|
||||
!isHusband && isMarried && fertilityGoal != FertilityGoal.justTracking && fertilityGoal != null;
|
||||
|
||||
/// Should show intimacy recommendations
|
||||
bool get showIntimacyContent => isMarried;
|
||||
|
||||
/// Copy with updated fields
|
||||
UserProfile copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
RelationshipStatus? relationshipStatus,
|
||||
FertilityGoal? fertilityGoal,
|
||||
int? averageCycleLength,
|
||||
int? averagePeriodLength,
|
||||
DateTime? lastPeriodStartDate,
|
||||
bool? notificationsEnabled,
|
||||
String? devotionalTime,
|
||||
bool? hasCompletedOnboarding,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? partnerName,
|
||||
|
||||
UserRole? role,
|
||||
bool? isIrregularCycle,
|
||||
}) {
|
||||
return UserProfile(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
relationshipStatus: relationshipStatus ?? this.relationshipStatus,
|
||||
fertilityGoal: fertilityGoal ?? this.fertilityGoal,
|
||||
averageCycleLength: averageCycleLength ?? this.averageCycleLength,
|
||||
averagePeriodLength: averagePeriodLength ?? this.averagePeriodLength,
|
||||
lastPeriodStartDate: lastPeriodStartDate ?? this.lastPeriodStartDate,
|
||||
notificationsEnabled: notificationsEnabled ?? this.notificationsEnabled,
|
||||
devotionalTime: devotionalTime ?? this.devotionalTime,
|
||||
hasCompletedOnboarding: hasCompletedOnboarding ?? this.hasCompletedOnboarding,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? DateTime.now(),
|
||||
partnerName: partnerName ?? this.partnerName,
|
||||
|
||||
role: role ?? this.role,
|
||||
isIrregularCycle: isIrregularCycle ?? this.isIrregularCycle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 8)
|
||||
enum UserRole {
|
||||
@HiveField(0)
|
||||
wife,
|
||||
@HiveField(1)
|
||||
husband,
|
||||
}
|
||||
210
lib/models/user_profile.g.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_profile.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class UserProfileAdapter extends TypeAdapter<UserProfile> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
UserProfile read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return UserProfile(
|
||||
id: fields[0] as String,
|
||||
name: fields[1] as String,
|
||||
relationshipStatus: fields[2] as RelationshipStatus,
|
||||
fertilityGoal: fields[3] as FertilityGoal?,
|
||||
averageCycleLength: fields[4] as int,
|
||||
averagePeriodLength: fields[5] as int,
|
||||
lastPeriodStartDate: fields[6] as DateTime?,
|
||||
notificationsEnabled: fields[7] as bool,
|
||||
devotionalTime: fields[8] as String?,
|
||||
hasCompletedOnboarding: fields[9] as bool,
|
||||
createdAt: fields[10] as DateTime,
|
||||
updatedAt: fields[11] as DateTime,
|
||||
partnerName: fields[12] as String?,
|
||||
role: fields[14] == null ? UserRole.wife : fields[14] as UserRole,
|
||||
isIrregularCycle: fields[15] == null ? false : fields[15] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserProfile obj) {
|
||||
writer
|
||||
..writeByte(15)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.name)
|
||||
..writeByte(2)
|
||||
..write(obj.relationshipStatus)
|
||||
..writeByte(3)
|
||||
..write(obj.fertilityGoal)
|
||||
..writeByte(4)
|
||||
..write(obj.averageCycleLength)
|
||||
..writeByte(5)
|
||||
..write(obj.averagePeriodLength)
|
||||
..writeByte(6)
|
||||
..write(obj.lastPeriodStartDate)
|
||||
..writeByte(7)
|
||||
..write(obj.notificationsEnabled)
|
||||
..writeByte(8)
|
||||
..write(obj.devotionalTime)
|
||||
..writeByte(9)
|
||||
..write(obj.hasCompletedOnboarding)
|
||||
..writeByte(10)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(11)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(12)
|
||||
..write(obj.partnerName)
|
||||
..writeByte(14)
|
||||
..write(obj.role)
|
||||
..writeByte(15)
|
||||
..write(obj.isIrregularCycle);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserProfileAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class RelationshipStatusAdapter extends TypeAdapter<RelationshipStatus> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
RelationshipStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return RelationshipStatus.single;
|
||||
case 1:
|
||||
return RelationshipStatus.engaged;
|
||||
case 2:
|
||||
return RelationshipStatus.married;
|
||||
default:
|
||||
return RelationshipStatus.single;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RelationshipStatus obj) {
|
||||
switch (obj) {
|
||||
case RelationshipStatus.single:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case RelationshipStatus.engaged:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case RelationshipStatus.married:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RelationshipStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class FertilityGoalAdapter extends TypeAdapter<FertilityGoal> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
FertilityGoal read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return FertilityGoal.tryingToConceive;
|
||||
case 1:
|
||||
return FertilityGoal.tryingToAvoid;
|
||||
case 2:
|
||||
return FertilityGoal.justTracking;
|
||||
default:
|
||||
return FertilityGoal.tryingToConceive;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, FertilityGoal obj) {
|
||||
switch (obj) {
|
||||
case FertilityGoal.tryingToConceive:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case FertilityGoal.tryingToAvoid:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case FertilityGoal.justTracking:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FertilityGoalAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class UserRoleAdapter extends TypeAdapter<UserRole> {
|
||||
@override
|
||||
final int typeId = 8;
|
||||
|
||||
@override
|
||||
UserRole read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return UserRole.wife;
|
||||
case 1:
|
||||
return UserRole.husband;
|
||||
default:
|
||||
return UserRole.wife;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserRole obj) {
|
||||
switch (obj) {
|
||||
case UserRole.wife:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case UserRole.husband:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserRoleAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
75
lib/providers/user_provider.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import '../models/user_profile.dart';
|
||||
import '../models/cycle_entry.dart';
|
||||
import '../services/cycle_service.dart';
|
||||
|
||||
/// Provider for the user profile
|
||||
final userProfileProvider = StateNotifierProvider<UserProfileNotifier, UserProfile?>((ref) {
|
||||
return UserProfileNotifier();
|
||||
});
|
||||
|
||||
/// Notifier for the user profile
|
||||
class UserProfileNotifier extends StateNotifier<UserProfile?> {
|
||||
UserProfileNotifier() : super(null) {
|
||||
_loadProfile();
|
||||
}
|
||||
|
||||
void _loadProfile() {
|
||||
final box = Hive.box<UserProfile>('user_profile');
|
||||
state = box.get('current_user');
|
||||
}
|
||||
|
||||
Future<void> updateProfile(UserProfile profile) async {
|
||||
final box = Hive.box<UserProfile>('user_profile');
|
||||
await box.put('current_user', profile);
|
||||
state = profile;
|
||||
}
|
||||
|
||||
Future<void> clearProfile() async {
|
||||
final box = Hive.box<UserProfile>('user_profile');
|
||||
await box.clear();
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider for cycle entries
|
||||
final cycleEntriesProvider = StateNotifierProvider<CycleEntriesNotifier, List<CycleEntry>>((ref) {
|
||||
return CycleEntriesNotifier();
|
||||
});
|
||||
|
||||
/// Notifier for cycle entries
|
||||
class CycleEntriesNotifier extends StateNotifier<List<CycleEntry>> {
|
||||
CycleEntriesNotifier() : super([]) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
void _loadEntries() {
|
||||
final box = Hive.box<CycleEntry>('cycle_entries');
|
||||
state = box.values.toList()..sort((a, b) => b.date.compareTo(a.date));
|
||||
}
|
||||
|
||||
Future<void> addEntry(CycleEntry entry) async {
|
||||
final box = Hive.box<CycleEntry>('cycle_entries');
|
||||
await box.put(entry.id, entry);
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(String id) async {
|
||||
final box = Hive.box<CycleEntry>('cycle_entries');
|
||||
await box.delete(id);
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
Future<void> clearEntries() async {
|
||||
final box = Hive.box<CycleEntry>('cycle_entries');
|
||||
await box.clear();
|
||||
state = [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Computed provider for current cycle info
|
||||
final currentCycleInfoProvider = Provider((ref) {
|
||||
final user = ref.watch(userProfileProvider);
|
||||
return CycleService.calculateCycleInfo(user);
|
||||
});
|
||||
412
lib/screens/calendar/calendar_screen.dart
Normal file
@@ -0,0 +1,412 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import '../../models/user_profile.dart';
|
||||
import '../../models/cycle_entry.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../services/cycle_service.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
|
||||
class CalendarScreen extends ConsumerStatefulWidget {
|
||||
const CalendarScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<CalendarScreen> createState() => _CalendarScreenState();
|
||||
}
|
||||
|
||||
class _CalendarScreenState extends ConsumerState<CalendarScreen> {
|
||||
DateTime _focusedDay = DateTime.now();
|
||||
DateTime? _selectedDay;
|
||||
CalendarFormat _calendarFormat = CalendarFormat.month;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entries = ref.watch(cycleEntriesProvider);
|
||||
final user = ref.watch(userProfileProvider);
|
||||
final cycleLength = user?.averageCycleLength ?? 28;
|
||||
final lastPeriodStart = user?.lastPeriodStartDate;
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Calendar',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildLegendButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.05),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TableCalendar(
|
||||
firstDay: DateTime.now().subtract(const Duration(days: 365)),
|
||||
lastDay: DateTime.now().add(const Duration(days: 365)),
|
||||
focusedDay: _focusedDay,
|
||||
calendarFormat: _calendarFormat,
|
||||
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
|
||||
onDaySelected: (selectedDay, focusedDay) {
|
||||
setState(() {
|
||||
_selectedDay = selectedDay;
|
||||
_focusedDay = focusedDay;
|
||||
});
|
||||
},
|
||||
onFormatChanged: (format) {
|
||||
setState(() => _calendarFormat = format);
|
||||
},
|
||||
onPageChanged: (focusedDay) {
|
||||
_focusedDay = focusedDay;
|
||||
},
|
||||
calendarStyle: CalendarStyle(
|
||||
outsideDaysVisible: false,
|
||||
defaultTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
weekendTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
todayDecoration: BoxDecoration(
|
||||
color: AppColors.sageGreen.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
todayTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.sageGreen,
|
||||
),
|
||||
selectedDecoration: const BoxDecoration(
|
||||
color: AppColors.sageGreen,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
selectedTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
headerStyle: HeaderStyle(
|
||||
formatButtonVisible: false,
|
||||
titleCentered: true,
|
||||
titleTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
leftChevronIcon: Icon(
|
||||
Icons.chevron_left,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
rightChevronIcon: Icon(
|
||||
Icons.chevron_right,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
daysOfWeekStyle: DaysOfWeekStyle(
|
||||
weekdayStyle: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
weekendStyle: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
calendarBuilders: CalendarBuilders(
|
||||
markerBuilder: (context, date, events) {
|
||||
// Check if it's a logged period day
|
||||
final isLoggedPeriod = _isLoggedPeriodDay(date, entries);
|
||||
|
||||
if (isLoggedPeriod) {
|
||||
return Positioned(
|
||||
bottom: 1,
|
||||
child: Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.menstrualPhase,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final phase = _getPhaseForDate(date, lastPeriodStart, cycleLength);
|
||||
if (phase != null) {
|
||||
return Positioned(
|
||||
bottom: 1,
|
||||
child: Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: _getPhaseColor(phase).withOpacity(0.5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Selected Day Info
|
||||
if (_selectedDay != null)
|
||||
Expanded(
|
||||
child: _buildDayInfo(_selectedDay!, lastPeriodStart, cycleLength, entries),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLegendButton() {
|
||||
return GestureDetector(
|
||||
onTap: () => _showLegend(),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blushPink.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, size: 16, color: AppColors.rose),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Legend',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.rose,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLegend() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (context) => Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Legend',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildLegendItem(AppColors.menstrualPhase, 'Period'),
|
||||
_buildLegendItem(AppColors.follicularPhase, 'Follicular Phase'),
|
||||
_buildLegendItem(AppColors.ovulationPhase, 'Ovulation Window'),
|
||||
_buildLegendItem(AppColors.lutealPhase, 'Luteal Phase'),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLegendItem(Color color, String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDayInfo(DateTime date, DateTime? lastPeriodStart, int cycleLength, List<CycleEntry> entries) {
|
||||
final phase = _getPhaseForDate(date, lastPeriodStart, cycleLength);
|
||||
final entry = _getEntryForDate(date, entries);
|
||||
final isLoggedPeriod = entry?.isPeriodDay ?? false;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${_getMonthName(date.month)} ${date.day}, ${date.year}',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (phase != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: _getPhaseColor(phase).withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(phase.emoji),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
phase.label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _getPhaseColor(phase),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (isLoggedPeriod)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.menstrualPhase.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.menstrualPhase.withOpacity(0.3)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.water_drop, color: AppColors.menstrualPhase, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Period Recorded',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.menstrualPhase,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
phase?.description ?? 'No cycle data for this date',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
CyclePhase? _getPhaseForDate(DateTime date, DateTime? lastPeriodStart, int cycleLength) {
|
||||
if (lastPeriodStart == null) return null;
|
||||
|
||||
final daysSinceLastPeriod = date.difference(lastPeriodStart).inDays;
|
||||
if (daysSinceLastPeriod < 0) return null;
|
||||
|
||||
final dayOfCycle = (daysSinceLastPeriod % cycleLength) + 1;
|
||||
|
||||
if (dayOfCycle <= 5) return CyclePhase.menstrual;
|
||||
if (dayOfCycle <= 13) return CyclePhase.follicular;
|
||||
if (dayOfCycle <= 16) return CyclePhase.ovulation;
|
||||
return CyclePhase.luteal;
|
||||
}
|
||||
|
||||
Color _getPhaseColor(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return AppColors.menstrualPhase;
|
||||
case CyclePhase.follicular:
|
||||
return AppColors.follicularPhase;
|
||||
case CyclePhase.ovulation:
|
||||
return AppColors.ovulationPhase;
|
||||
case CyclePhase.luteal:
|
||||
return AppColors.lutealPhase;
|
||||
}
|
||||
}
|
||||
|
||||
String _getMonthName(int month) {
|
||||
const months = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
return months[month - 1];
|
||||
}
|
||||
|
||||
bool _isLoggedPeriodDay(DateTime date, List<CycleEntry> entries) {
|
||||
final entry = _getEntryForDate(date, entries);
|
||||
return entry?.isPeriodDay ?? false;
|
||||
}
|
||||
|
||||
CycleEntry? _getEntryForDate(DateTime date, List<CycleEntry> entries) {
|
||||
try {
|
||||
return entries.firstWhere(
|
||||
(entry) => isSameDay(entry.date, date),
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
372
lib/screens/devotional/devotional_screen.dart
Normal file
@@ -0,0 +1,372 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../models/scripture.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../services/cycle_service.dart';
|
||||
import '../../models/cycle_entry.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
|
||||
class DevotionalScreen extends ConsumerWidget {
|
||||
const DevotionalScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final user = ref.watch(userProfileProvider);
|
||||
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
||||
|
||||
final phase = cycleInfo['phase'] as CyclePhase;
|
||||
final scripture = ScriptureDatabase.getScriptureForPhase(phase.name);
|
||||
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Today\'s Devotional',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _getPhaseColor(phase).withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(phase.emoji),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
phase.label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _getPhaseColor(phase),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
phase.description,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Main Scripture Card
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
_getPhaseColor(phase).withOpacity(0.15),
|
||||
AppColors.cream,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: _getPhaseColor(phase).withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Quote icon
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: _getPhaseColor(phase).withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.format_quote,
|
||||
color: _getPhaseColor(phase),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Verse
|
||||
Text(
|
||||
'"${scripture.verse}"',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 20,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Reference
|
||||
Text(
|
||||
'— ${scripture.reference}',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Reflection
|
||||
if (scripture.reflection != null) ...[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.lightbulb_outline,
|
||||
color: AppColors.softGold,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Reflection',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
scripture.reflection!,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Phase-specific encouragement
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.favorite_outline,
|
||||
color: AppColors.rose,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'For Your ${phase.label} Phase',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_getPhaseEncouragement(phase, user?.isMarried ?? false),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Prayer Prompt
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColors.lavender.withOpacity(0.2),
|
||||
AppColors.blushPink.withOpacity(0.2),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('🙏', style: TextStyle(fontSize: 20)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Prayer Prompt',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_getPrayerPrompt(phase),
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 14,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Action buttons
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.share_outlined),
|
||||
label: const Text('Share'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.edit_note),
|
||||
label: const Text('Journal'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Placeholder _getCurrentPhase removed as it's now in CycleService
|
||||
|
||||
Color _getPhaseColor(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return AppColors.menstrualPhase;
|
||||
case CyclePhase.follicular:
|
||||
return AppColors.follicularPhase;
|
||||
case CyclePhase.ovulation:
|
||||
return AppColors.ovulationPhase;
|
||||
case CyclePhase.luteal:
|
||||
return AppColors.lutealPhase;
|
||||
}
|
||||
}
|
||||
|
||||
String _getPhaseEncouragement(CyclePhase phase, bool isMarried) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'Your body is renewing itself. This is a sacred time for rest and reflection. '
|
||||
'Don\'t push yourself too hard—God designed this phase for slowing down. '
|
||||
'Use this time to draw near to Him in quietness.';
|
||||
case CyclePhase.follicular:
|
||||
return 'Energy is returning! Your body is preparing for the days ahead. '
|
||||
'This is a wonderful time to tackle projects, connect with friends, and serve others. '
|
||||
'Let your renewed strength be used for His purposes.';
|
||||
case CyclePhase.ovulation:
|
||||
if (isMarried) {
|
||||
return 'You are in your most fertile window. Whether you\'re hoping to conceive or practicing NFP, '
|
||||
'remember that God is sovereign over the womb. Trust His timing and purposes for your family.';
|
||||
}
|
||||
return 'You may feel more confident and social during this phase. '
|
||||
'It\'s a great time for important conversations, presentations, or stepping out in faith. '
|
||||
'Let your light shine before others.';
|
||||
case CyclePhase.luteal:
|
||||
return 'The luteal phase can bring challenging emotions and PMS symptoms. '
|
||||
'Be patient with yourself. This is not weakness—it\'s your body doing what God designed. '
|
||||
'Lean into His peace that surpasses understanding.';
|
||||
}
|
||||
}
|
||||
|
||||
String _getPrayerPrompt(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return '"Lord, thank You for designing my body with such wisdom. '
|
||||
'Help me to rest in You during this time and to trust that You are renewing me. '
|
||||
'May I find my strength in Your presence. Amen."';
|
||||
case CyclePhase.follicular:
|
||||
return '"Father, thank You for this season of renewed energy. '
|
||||
'Guide me to use this strength for Your glory and the good of others. '
|
||||
'Help me to serve with joy and purpose. Amen."';
|
||||
case CyclePhase.ovulation:
|
||||
return '"Creator God, I am fearfully and wonderfully made. '
|
||||
'Thank You for the gift of womanhood. '
|
||||
'Help me to honor You in all I do today. Amen."';
|
||||
case CyclePhase.luteal:
|
||||
return '"Lord, I bring my anxious thoughts to You. '
|
||||
'When my emotions feel overwhelming, remind me of Your peace. '
|
||||
'Help me to be gentle with myself as You are gentle with me. Amen."';
|
||||
}
|
||||
}
|
||||
}
|
||||
406
lib/screens/home/home_screen.dart
Normal file
@@ -0,0 +1,406 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
import '../../models/user_profile.dart';
|
||||
import '../../models/cycle_entry.dart';
|
||||
import '../../models/scripture.dart';
|
||||
import '../calendar/calendar_screen.dart';
|
||||
import '../log/log_screen.dart';
|
||||
import '../devotional/devotional_screen.dart';
|
||||
import '../../widgets/tip_card.dart';
|
||||
import '../../widgets/cycle_ring.dart';
|
||||
import '../../widgets/scripture_card.dart';
|
||||
import '../../widgets/quick_log_buttons.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../services/cycle_service.dart';
|
||||
|
||||
class HomeScreen extends ConsumerStatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends ConsumerState<HomeScreen> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: IndexedStack(
|
||||
index: _selectedIndex,
|
||||
children: [
|
||||
const _DashboardTab(),
|
||||
const CalendarScreen(),
|
||||
const LogScreen(),
|
||||
const DevotionalScreen(),
|
||||
_SettingsTab(onReset: () => setState(() => _selectedIndex = 0)),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
currentIndex: _selectedIndex,
|
||||
onTap: (index) => setState(() => _selectedIndex = index),
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
activeIcon: Icon(Icons.home),
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.calendar_today_outlined),
|
||||
activeIcon: Icon(Icons.calendar_today),
|
||||
label: 'Calendar',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.add_circle_outline),
|
||||
activeIcon: Icon(Icons.add_circle),
|
||||
label: 'Log',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.menu_book_outlined),
|
||||
activeIcon: Icon(Icons.menu_book),
|
||||
label: 'Devotional',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
activeIcon: Icon(Icons.settings),
|
||||
label: 'Settings',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DashboardTab extends ConsumerWidget {
|
||||
const _DashboardTab();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final user = ref.watch(userProfileProvider);
|
||||
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
||||
|
||||
final name = user?.name ?? 'Friend';
|
||||
final phase = cycleInfo['phase'] as CyclePhase;
|
||||
final dayOfCycle = cycleInfo['dayOfCycle'] as int;
|
||||
final cycleLength = user?.averageCycleLength ?? 28;
|
||||
|
||||
// Get scripture for current phase
|
||||
final scripture = ScriptureDatabase.getScriptureForPhase(phase.name);
|
||||
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Greeting
|
||||
_buildGreeting(name),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Cycle Ring
|
||||
Center(
|
||||
child: CycleRing(
|
||||
dayOfCycle: dayOfCycle,
|
||||
totalDays: cycleLength,
|
||||
phase: phase,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Scripture Card
|
||||
ScriptureCard(
|
||||
verse: scripture.verse,
|
||||
reference: scripture.reference,
|
||||
phase: phase,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Quick Log Buttons
|
||||
Text(
|
||||
'Quick Log',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const QuickLogButtons(),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Today's Tip - Only show if not just tracking or husband (though husband has own screen)
|
||||
if (user?.role == UserRole.wife)
|
||||
TipCard(phase: phase, isMarried: user?.isMarried ?? false),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGreeting(String name) {
|
||||
final hour = DateTime.now().hour;
|
||||
String greeting;
|
||||
if (hour < 12) {
|
||||
greeting = 'Good morning';
|
||||
} else if (hour < 17) {
|
||||
greeting = 'Good afternoon';
|
||||
} else {
|
||||
greeting = 'Good evening';
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$greeting,',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
name,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blushPink,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: AppColors.rose,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Placeholder _calculateCycleInfo removed as it's now in CycleService
|
||||
}
|
||||
|
||||
class _SettingsTab extends ConsumerWidget {
|
||||
final VoidCallback? onReset;
|
||||
const _SettingsTab({this.onReset});
|
||||
|
||||
Widget _buildSettingsTile(BuildContext context, IconData icon, String title, {VoidCallback? onTap}) {
|
||||
return ListTile(
|
||||
leading: Icon(icon, color: AppColors.charcoal),
|
||||
title: Text(
|
||||
title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
trailing: Icon(Icons.chevron_right, color: AppColors.lightGray),
|
||||
onTap: onTap ?? () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Settings coming soon!')),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _resetApp(BuildContext context, WidgetRef ref) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Reset App?'),
|
||||
content: const Text('This will clear all data and return you to onboarding. Are you sure?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Reset', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
await ref.read(userProfileProvider.notifier).clearProfile();
|
||||
await ref.read(cycleEntriesProvider.notifier).clearEntries();
|
||||
|
||||
if (context.mounted) {
|
||||
onReset?.call();
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final user = ref.watch(userProfileProvider);
|
||||
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Settings',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Profile Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColors.blushPink, AppColors.rose.withOpacity(0.7)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
user?.name.isNotEmpty == true ? user!.name[0].toUpperCase() : '?',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
user?.name ?? 'Guest',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
user?.role == UserRole.husband
|
||||
? 'HUSBAND'
|
||||
: (user?.relationshipStatus.name.toUpperCase() ?? 'SINGLE'),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
letterSpacing: 1,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.chevron_right, color: AppColors.warmGray),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Settings Groups
|
||||
_buildSettingsGroup('Preferences', [
|
||||
_buildSettingsTile(context, Icons.notifications_outlined, 'Notifications'),
|
||||
_buildSettingsTile(context, Icons.palette_outlined, 'Appearance'),
|
||||
_buildSettingsTile(context, Icons.lock_outline, 'Privacy'),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildSettingsGroup('Cycle', [
|
||||
_buildSettingsTile(context, Icons.calendar_today_outlined, 'Cycle Settings'),
|
||||
_buildSettingsTile(context, Icons.trending_up_outlined, 'Cycle History'),
|
||||
_buildSettingsTile(context, Icons.download_outlined, 'Export Data'),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
_buildSettingsGroup('Account', [
|
||||
_buildSettingsTile(
|
||||
context,
|
||||
Icons.logout,
|
||||
'Reset App / Logout',
|
||||
onTap: () => _resetApp(context, ref)
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingsGroup(String title, List<Widget> tiles) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: tiles,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
831
lib/screens/husband/husband_home_screen.dart
Normal file
@@ -0,0 +1,831 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
import '../../models/cycle_entry.dart';
|
||||
import '../../models/scripture.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../services/cycle_service.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
/// Husband's companion app main screen
|
||||
class HusbandHomeScreen extends ConsumerStatefulWidget {
|
||||
const HusbandHomeScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HusbandHomeScreen> createState() => _HusbandHomeScreenState();
|
||||
}
|
||||
|
||||
class _HusbandHomeScreenState extends ConsumerState<HusbandHomeScreen> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Theme(
|
||||
data: _husbandTheme,
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.warmCream,
|
||||
body: IndexedStack(
|
||||
index: _selectedIndex,
|
||||
children: [
|
||||
const _HusbandDashboard(),
|
||||
const _HusbandTipsScreen(),
|
||||
const _HusbandLearnScreen(),
|
||||
const _HusbandSettingsScreen(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.navyBlue.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
currentIndex: _selectedIndex,
|
||||
onTap: (index) => setState(() => _selectedIndex = index),
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: AppColors.navyBlue,
|
||||
unselectedItemColor: AppColors.warmGray,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
activeIcon: Icon(Icons.home),
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.lightbulb_outline),
|
||||
activeIcon: Icon(Icons.lightbulb),
|
||||
label: 'Tips',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.school_outlined),
|
||||
activeIcon: Icon(Icons.school),
|
||||
label: 'Learn',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
activeIcon: Icon(Icons.settings),
|
||||
label: 'Settings',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData get _husbandTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: AppColors.warmCream,
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: AppColors.navyBlue,
|
||||
secondary: AppColors.gold,
|
||||
surface: AppColors.warmCream,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: AppColors.warmCream,
|
||||
foregroundColor: AppColors.navyBlue,
|
||||
elevation: 0,
|
||||
titleTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.navyBlue,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HusbandDashboard extends ConsumerWidget {
|
||||
const _HusbandDashboard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final user = ref.watch(userProfileProvider);
|
||||
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
||||
|
||||
final wifeName = user?.partnerName ?? "Wife";
|
||||
final phase = cycleInfo['phase'] as CyclePhase;
|
||||
final dayOfCycle = cycleInfo['dayOfCycle'] as int;
|
||||
final daysUntilPeriod = cycleInfo['daysUntilPeriod'] as int;
|
||||
|
||||
final scripture = ScriptureDatabase.getHusbandScripture();
|
||||
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Greeting
|
||||
Text(
|
||||
'Hey there,',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Husband',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Wife's Cycle Status
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColors.navyBlue,
|
||||
AppColors.steelBlue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.white,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'$wifeName\'s Cycle',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Day $dayOfCycle • ${phase.label}',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
daysUntilPeriod > 0
|
||||
? '~$daysUntilPeriod days until period'
|
||||
: 'Period expected soon',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(phase.emoji),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
_getPhaseHint(phase),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Support Tip
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.navyBlue.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.gold.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
color: AppColors.gold,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'How to Support Her',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_getSupportTip(phase),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Scripture for Husbands
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColors.gold.withOpacity(0.15),
|
||||
AppColors.warmCream,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppColors.gold.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.menu_book,
|
||||
color: AppColors.gold,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Scripture for Husbands',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'"${scripture.verse}"',
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 15,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.navyBlue,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'— ${scripture.reference}',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Prayer Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _showPrayerPrompt(context, phase),
|
||||
icon: const Text('🙏', style: TextStyle(fontSize: 18)),
|
||||
label: Text(
|
||||
'Pray for ${wifeName}',
|
||||
style: GoogleFonts.outfit(fontWeight: FontWeight.w500),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.navyBlue,
|
||||
side: const BorderSide(color: AppColors.navyBlue),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getPhaseHint(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'She may need extra rest';
|
||||
case CyclePhase.follicular:
|
||||
return 'Energy is returning';
|
||||
case CyclePhase.ovulation:
|
||||
return 'Fertile window';
|
||||
case CyclePhase.luteal:
|
||||
return 'PMS may occur';
|
||||
}
|
||||
}
|
||||
|
||||
String _getSupportTip(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'This is a time when she needs extra care. Help with household tasks without being asked. '
|
||||
'Bring her favorite warm drink, suggest low-key activities, and be extra patient.';
|
||||
case CyclePhase.follicular:
|
||||
return 'Her energy is returning! This is a great time to plan dates, work on projects together, '
|
||||
'and affirm her strengths. She may be more talkative and social.';
|
||||
case CyclePhase.ovulation:
|
||||
return 'Prioritize connection time. Romance and quality time matter. '
|
||||
'If you\'re trying to conceive, this is your fertile window.';
|
||||
case CyclePhase.luteal:
|
||||
return 'Be patient—PMS may affect her mood. Listen more, "fix" less. '
|
||||
'Take initiative on responsibilities and surprise her with comfort foods.';
|
||||
}
|
||||
}
|
||||
|
||||
void _showPrayerPrompt(BuildContext context, CyclePhase phase) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (context) => Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'🙏 Prayer for Your Wife',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
_getPrayer(phase),
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.navyBlue,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Amen'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getPrayer(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return '"Lord, I lift up my wife during this time of rest. '
|
||||
'Give her body the renewal it needs and grant her Your peace. '
|
||||
'Help me to serve her with patience and love. Amen."';
|
||||
case CyclePhase.follicular:
|
||||
return '"Father, thank You for my wife\'s renewed energy. '
|
||||
'Bless her endeavors and help me to encourage and support her. '
|
||||
'May our partnership glorify You. Amen."';
|
||||
case CyclePhase.ovulation:
|
||||
return '"Creator God, You have designed my wife fearfully and wonderfully. '
|
||||
'Whatever Your plans for our family, help us trust Your timing. '
|
||||
'Bless our marriage and intimacy. Amen."';
|
||||
case CyclePhase.luteal:
|
||||
return '"Lord, be near to my wife during this phase. '
|
||||
'When emotions are difficult, grant her Your peace that passes understanding. '
|
||||
'Help me to be patient, kind, and understanding. Amen."';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HusbandTipsScreen extends StatelessWidget {
|
||||
const _HusbandTipsScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Supporting Her',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Practical ways to love your wife well',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildTipCategory('During Her Period', [
|
||||
'🏠 Help with household tasks without being asked',
|
||||
'🍵 Bring her favorite comfort drink',
|
||||
'📺 Suggest low-key activities (movies, quiet time)',
|
||||
'🙏 Pray for her physical comfort',
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildTipCategory('Follicular Phase', [
|
||||
'🎉 Plan dates or activities—her energy is returning',
|
||||
'💬 She may be more talkative and social',
|
||||
'💪 Great time for projects together',
|
||||
'❤️ Affirm her strengths and beauty',
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildTipCategory('Luteal Phase (PMS)', [
|
||||
'😌 Be patient—PMS may affect her mood',
|
||||
'🍫 Surprise with comfort foods',
|
||||
'🧹 Take initiative on responsibilities',
|
||||
'👂 Listen more, "fix" less',
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildTipCategory('General Wisdom', [
|
||||
'🗣️ Ask how she\'s feeling—and actually listen',
|
||||
'📱 Put your phone down when she\'s talking',
|
||||
'🌹 Small gestures matter more than grand ones',
|
||||
'🙏 Pray for her daily',
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTipCategory(String title, List<String> tips) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...tips.map((tip) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
tip,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HusbandLearnScreen extends StatelessWidget {
|
||||
const _HusbandLearnScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Learn',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildSection('Understanding Her', [
|
||||
_LearnItem(
|
||||
icon: Icons.loop,
|
||||
title: 'The 4 Phases of Her Cycle',
|
||||
subtitle: 'What\'s happening in her body each month',
|
||||
),
|
||||
_LearnItem(
|
||||
icon: Icons.psychology_outlined,
|
||||
title: 'Why Does Her Mood Change?',
|
||||
subtitle: 'Hormones explained simply',
|
||||
),
|
||||
_LearnItem(
|
||||
icon: Icons.medical_information_outlined,
|
||||
title: 'PMS is Real',
|
||||
subtitle: 'Medical facts for supportive husbands',
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildSection('Biblical Manhood', [
|
||||
_LearnItem(
|
||||
icon: Icons.favorite,
|
||||
title: 'Loving Like Christ',
|
||||
subtitle: 'Ephesians 5 in daily practice',
|
||||
),
|
||||
_LearnItem(
|
||||
icon: Icons.handshake,
|
||||
title: 'Servant Leadership at Home',
|
||||
subtitle: 'What it really means',
|
||||
),
|
||||
_LearnItem(
|
||||
icon: Icons.auto_awesome,
|
||||
title: 'Praying for Your Wife',
|
||||
subtitle: 'Practical guide',
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildSection('NFP for Husbands', [
|
||||
_LearnItem(
|
||||
icon: Icons.show_chart,
|
||||
title: 'Reading the Charts Together',
|
||||
subtitle: 'Understanding fertility signs',
|
||||
),
|
||||
_LearnItem(
|
||||
icon: Icons.schedule,
|
||||
title: 'Abstinence as Spiritual Discipline',
|
||||
subtitle: 'Growing together during fertile days',
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(String title, List<_LearnItem> items) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: items
|
||||
.map((item) => ListTile(
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.navyBlue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
item.icon,
|
||||
color: AppColors.navyBlue,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
item.title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
item.subtitle,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 13,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
color: AppColors.lightGray,
|
||||
),
|
||||
onTap: () {},
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LearnItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
const _LearnItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
});
|
||||
}
|
||||
|
||||
class _HusbandSettingsScreen extends ConsumerWidget {
|
||||
const _HusbandSettingsScreen();
|
||||
|
||||
Future<void> _resetApp(BuildContext context, WidgetRef ref) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Reset App?'),
|
||||
content: const Text('This will clear all data and return you to onboarding. Are you sure?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Reset', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
await ref.read(userProfileProvider.notifier).clearProfile();
|
||||
await ref.read(cycleEntriesProvider.notifier).clearEntries();
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Settings',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.notifications_outlined),
|
||||
title: Text('Notifications', style: GoogleFonts.outfit()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.link_outlined),
|
||||
title: Text('Connection', style: GoogleFonts.outfit()),
|
||||
subtitle: Text('Linked with wife\'s app', style: GoogleFonts.outfit(fontSize: 12)),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text('Reset App / Logout', style: GoogleFonts.outfit()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _resetApp(context, ref),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.help_outline),
|
||||
title: Text('Help & Support', style: GoogleFonts.outfit()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
450
lib/screens/log/log_screen.dart
Normal file
@@ -0,0 +1,450 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../models/cycle_entry.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class LogScreen extends ConsumerStatefulWidget {
|
||||
const LogScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<LogScreen> createState() => _LogScreenState();
|
||||
}
|
||||
|
||||
class _LogScreenState extends ConsumerState<LogScreen> {
|
||||
bool _isPeriodDay = false;
|
||||
FlowIntensity? _flowIntensity;
|
||||
MoodLevel? _mood;
|
||||
int _energyLevel = 3;
|
||||
int _crampIntensity = 0;
|
||||
bool _hasHeadache = false;
|
||||
bool _hasBloating = false;
|
||||
bool _hasBreastTenderness = false;
|
||||
bool _hasFatigue = false;
|
||||
bool _hasAcne = false;
|
||||
final TextEditingController _notesController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _saveEntry() async {
|
||||
final entry = CycleEntry(
|
||||
id: const Uuid().v4(),
|
||||
date: DateTime.now(),
|
||||
isPeriodDay: _isPeriodDay,
|
||||
flowIntensity: _isPeriodDay ? _flowIntensity : null,
|
||||
mood: _mood,
|
||||
energyLevel: _energyLevel,
|
||||
crampIntensity: _crampIntensity > 0 ? _crampIntensity : null,
|
||||
hasHeadache: _hasHeadache,
|
||||
hasBloating: _hasBloating,
|
||||
hasBreastTenderness: _hasBreastTenderness,
|
||||
hasFatigue: _hasFatigue,
|
||||
hasAcne: _hasAcne,
|
||||
notes: _notesController.text.isNotEmpty ? _notesController.text : null,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await ref.read(cycleEntriesProvider.notifier).addEntry(entry);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Entry saved!', style: GoogleFonts.outfit()),
|
||||
backgroundColor: AppColors.sageGreen,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
);
|
||||
_resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
void _resetForm() {
|
||||
setState(() {
|
||||
_isPeriodDay = false;
|
||||
_flowIntensity = null;
|
||||
_mood = null;
|
||||
_energyLevel = 3;
|
||||
_crampIntensity = 0;
|
||||
_hasHeadache = false;
|
||||
_hasBloating = false;
|
||||
_hasBreastTenderness = false;
|
||||
_hasFatigue = false;
|
||||
_hasAcne = false;
|
||||
_notesController.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Text(
|
||||
'How are you feeling?',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDate(DateTime.now()),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Period Toggle
|
||||
_buildSectionCard(
|
||||
title: 'Period',
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Is today a period day?',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: _isPeriodDay,
|
||||
onChanged: (value) => setState(() => _isPeriodDay = value),
|
||||
activeColor: AppColors.menstrualPhase,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Flow Intensity (only if period day)
|
||||
if (_isPeriodDay) ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildSectionCard(
|
||||
title: 'Flow Intensity',
|
||||
child: Row(
|
||||
children: FlowIntensity.values.map((flow) {
|
||||
final isSelected = _flowIntensity == flow;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _flowIntensity = flow),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.menstrualPhase.withOpacity(0.2)
|
||||
: AppColors.lightGray.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: isSelected
|
||||
? Border.all(color: AppColors.menstrualPhase)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.water_drop,
|
||||
color: isSelected
|
||||
? AppColors.menstrualPhase
|
||||
: AppColors.warmGray,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
flow.label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 11,
|
||||
color: isSelected
|
||||
? AppColors.menstrualPhase
|
||||
: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Mood
|
||||
_buildSectionCard(
|
||||
title: 'Mood',
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: MoodLevel.values.map((mood) {
|
||||
final isSelected = _mood == mood;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _mood = mood),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.softGold.withOpacity(0.2)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isSelected
|
||||
? Border.all(color: AppColors.softGold)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
mood.emoji,
|
||||
style: TextStyle(
|
||||
fontSize: isSelected ? 32 : 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
mood.label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 10,
|
||||
color: isSelected
|
||||
? AppColors.softGold
|
||||
: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Energy Level
|
||||
_buildSectionCard(
|
||||
title: 'Energy Level',
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.battery_1_bar, color: AppColors.warmGray),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _energyLevel.toDouble(),
|
||||
min: 1,
|
||||
max: 5,
|
||||
divisions: 4,
|
||||
onChanged: (value) {
|
||||
setState(() => _energyLevel = value.round());
|
||||
},
|
||||
),
|
||||
),
|
||||
const Icon(Icons.battery_full, color: AppColors.sageGreen),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
_getEnergyLabel(_energyLevel),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 13,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Symptoms
|
||||
_buildSectionCard(
|
||||
title: 'Symptoms',
|
||||
child: Column(
|
||||
children: [
|
||||
// Cramps Slider
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
'Cramps',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _crampIntensity.toDouble(),
|
||||
min: 0,
|
||||
max: 5,
|
||||
divisions: 5,
|
||||
activeColor: AppColors.rose,
|
||||
onChanged: (value) {
|
||||
setState(() => _crampIntensity = value.round());
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Text(
|
||||
_crampIntensity == 0 ? 'None' : '$_crampIntensity/5',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Symptom Toggles
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildSymptomChip('Headache', _hasHeadache, (v) => setState(() => _hasHeadache = v)),
|
||||
_buildSymptomChip('Bloating', _hasBloating, (v) => setState(() => _hasBloating = v)),
|
||||
_buildSymptomChip('Breast Tenderness', _hasBreastTenderness, (v) => setState(() => _hasBreastTenderness = v)),
|
||||
_buildSymptomChip('Fatigue', _hasFatigue, (v) => setState(() => _hasFatigue = v)),
|
||||
_buildSymptomChip('Acne', _hasAcne, (v) => setState(() => _hasAcne = v)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Notes
|
||||
_buildSectionCard(
|
||||
title: 'Notes',
|
||||
child: TextField(
|
||||
controller: _notesController,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Add any notes about how you\'re feeling...',
|
||||
hintStyle: GoogleFonts.outfit(
|
||||
color: AppColors.lightGray,
|
||||
fontSize: 14,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Save Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _saveEntry,
|
||||
child: const Text('Save Entry'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionCard({required String title, required Widget child}) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
child,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSymptomChip(String label, bool isSelected, ValueChanged<bool> onChanged) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!isSelected),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.lavender.withOpacity(0.3) : AppColors.lightGray.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: isSelected ? Border.all(color: AppColors.lavender) : null,
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 13,
|
||||
color: isSelected ? AppColors.ovulationPhase : AppColors.warmGray,
|
||||
fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
const months = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
return '${days[date.weekday - 1]}, ${months[date.month - 1]} ${date.day}';
|
||||
}
|
||||
|
||||
String _getEnergyLabel(int level) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return 'Very Low';
|
||||
case 2:
|
||||
return 'Low';
|
||||
case 3:
|
||||
return 'Normal';
|
||||
case 4:
|
||||
return 'Good';
|
||||
case 5:
|
||||
return 'Excellent';
|
||||
default:
|
||||
return 'Normal';
|
||||
}
|
||||
}
|
||||
}
|
||||
642
lib/screens/onboarding/onboarding_screen.dart
Normal file
@@ -0,0 +1,642 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
import 'package:christian_period_tracker/models/user_profile.dart';
|
||||
import 'package:christian_period_tracker/models/cycle_entry.dart';
|
||||
import '../home/home_screen.dart';
|
||||
import '../husband/husband_home_screen.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
|
||||
class OnboardingScreen extends ConsumerStatefulWidget {
|
||||
const OnboardingScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<OnboardingScreen> createState() => _OnboardingScreenState();
|
||||
}
|
||||
|
||||
class _OnboardingScreenState extends ConsumerState<OnboardingScreen> {
|
||||
final PageController _pageController = PageController();
|
||||
int _currentPage = 0;
|
||||
bool _isNavigating = false; // Debounce flag
|
||||
|
||||
// Form data
|
||||
UserRole _role = UserRole.wife;
|
||||
String _name = '';
|
||||
RelationshipStatus _relationshipStatus = RelationshipStatus.single;
|
||||
FertilityGoal? _fertilityGoal;
|
||||
int _averageCycleLength = 28;
|
||||
DateTime? _lastPeriodStart;
|
||||
bool _isIrregularCycle = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _nextPage() async {
|
||||
if (_isNavigating) return;
|
||||
_isNavigating = true;
|
||||
|
||||
// Husband Flow: Role (0) -> Name (1) -> Finish
|
||||
// Wife Flow: Role (0) -> Name (1) -> Relationship (2) -> [Fertility (3)] -> Cycle (4)
|
||||
|
||||
int nextPage = _currentPage + 1;
|
||||
|
||||
// Logic for skipping pages
|
||||
if (_role == UserRole.husband) {
|
||||
if (_currentPage == 1) {
|
||||
await _completeOnboarding();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Wife flow
|
||||
if (_currentPage == 2 && _relationshipStatus != RelationshipStatus.married) {
|
||||
// Skip fertility goal (page 3) if not married
|
||||
nextPage = 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextPage <= 4) { // Max pages
|
||||
await _pageController.animateToPage(
|
||||
nextPage,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
} else {
|
||||
await _completeOnboarding();
|
||||
}
|
||||
|
||||
// Reset debounce after animation
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) setState(() => _isNavigating = false);
|
||||
});
|
||||
}
|
||||
|
||||
void _previousPage() async {
|
||||
if (_isNavigating) return;
|
||||
_isNavigating = true;
|
||||
|
||||
int prevPage = _currentPage - 1;
|
||||
|
||||
// Logic for reverse skipping
|
||||
if (_role == UserRole.wife) {
|
||||
if (_currentPage == 4 && _relationshipStatus != RelationshipStatus.married) {
|
||||
// Skip back over fertility goal (page 3)
|
||||
prevPage = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevPage >= 0) {
|
||||
await _pageController.animateToPage(
|
||||
prevPage,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
// Reset debounce after animation
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) setState(() => _isNavigating = false);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _completeOnboarding() async {
|
||||
final userProfile = UserProfile(
|
||||
id: const Uuid().v4(),
|
||||
name: _name,
|
||||
role: _role,
|
||||
relationshipStatus: _role == UserRole.husband ? RelationshipStatus.married : _relationshipStatus,
|
||||
fertilityGoal: (_role == UserRole.wife && _relationshipStatus == RelationshipStatus.married) ? _fertilityGoal : null,
|
||||
averageCycleLength: _averageCycleLength,
|
||||
lastPeriodStartDate: _lastPeriodStart,
|
||||
isIrregularCycle: _isIrregularCycle,
|
||||
hasCompletedOnboarding: true,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await ref.read(userProfileProvider.notifier).updateProfile(userProfile);
|
||||
|
||||
if (mounted) {
|
||||
// Navigate to appropriate home screen
|
||||
if (_role == UserRole.husband) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const HusbandHomeScreen(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const HomeScreen()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Different background color for husband flow
|
||||
final bgColor = _role == UserRole.husband ? AppColors.warmCream : AppColors.cream;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: bgColor,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Progress indicator (hide on role page 0)
|
||||
if (_currentPage > 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SmoothPageIndicator(
|
||||
controller: _pageController,
|
||||
count: _role == UserRole.husband ? 2 : 5,
|
||||
effect: WormEffect(
|
||||
dotHeight: 8,
|
||||
dotWidth: 8,
|
||||
spacing: 12,
|
||||
activeDotColor: _role == UserRole.husband ? AppColors.navyBlue : AppColors.sageGreen,
|
||||
dotColor: AppColors.lightGray.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Pages
|
||||
Expanded(
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(), // Disable swipe
|
||||
onPageChanged: (index) {
|
||||
setState(() => _currentPage = index);
|
||||
},
|
||||
children: [
|
||||
_buildRolePage(), // Page 0
|
||||
_buildNamePage(), // Page 1
|
||||
_buildRelationshipPage(), // Page 2 (Wife only)
|
||||
_buildFertilityGoalPage(), // Page 3 (Wife married only)
|
||||
_buildCyclePage(), // Page 4 (Wife only)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRolePage() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColors.blushPink, AppColors.rose.withOpacity(0.7)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.favorite_rounded,
|
||||
size: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
'Who is this app for?',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
_buildRoleOption(UserRole.wife, 'For Her', 'Track cycle, health, and faith', Icons.female),
|
||||
const SizedBox(height: 16),
|
||||
_buildRoleOption(UserRole.husband, 'For Him', 'Support your wife and grow together', Icons.male),
|
||||
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _nextPage,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _role == UserRole.husband ? AppColors.navyBlue : AppColors.sageGreen,
|
||||
),
|
||||
child: const Text('Continue'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRoleOption(UserRole role, String title, String subtitle, IconData icon) {
|
||||
final isSelected = _role == role;
|
||||
// Dynamic colors based on role selection
|
||||
final activeColor = role == UserRole.wife ? AppColors.sageGreen : AppColors.navyBlue;
|
||||
final activeBg = role == UserRole.wife ? AppColors.sageGreen.withOpacity(0.1) : AppColors.navyBlue.withOpacity(0.1);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _role = role),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? activeBg : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected ? activeColor : AppColors.lightGray.withOpacity(0.5),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
boxShadow: isSelected ? [
|
||||
BoxShadow(
|
||||
color: activeColor.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
)
|
||||
] : [],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? activeColor : AppColors.lightGray.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: isSelected ? Colors.white : AppColors.warmGray,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
Icon(Icons.check_circle, color: activeColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNamePage() {
|
||||
final isHusband = _role == UserRole.husband;
|
||||
final activeColor = isHusband ? AppColors.navyBlue : AppColors.sageGreen;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
isHusband ? 'What\'s your name, sir?' : 'What\'s your name?',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isHusband ? AppColors.navyBlue : AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'We\'ll use this to personalize the app.',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
TextField(
|
||||
onChanged: (value) => setState(() => _name = value),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter your name',
|
||||
prefixIcon: Icon(
|
||||
Icons.person_outline,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: activeColor),
|
||||
),
|
||||
),
|
||||
style: GoogleFonts.outfit(fontSize: 16),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _previousPage,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: isHusband ? AppColors.navyBlue : AppColors.sageGreen,
|
||||
side: BorderSide(color: isHusband ? AppColors.navyBlue : AppColors.sageGreen),
|
||||
),
|
||||
child: const Text('Back'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: (_name.isNotEmpty && !_isNavigating) ? _nextPage : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: activeColor,
|
||||
),
|
||||
child: Text(isHusband ? 'Finish Setup' : 'Continue'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRelationshipPage() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
'Tell us about yourself',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
_buildRelationshipOption(RelationshipStatus.single, 'Single', 'Wellness focus', Icons.person_outline),
|
||||
const SizedBox(height: 12),
|
||||
_buildRelationshipOption(RelationshipStatus.engaged, 'Engaged', 'Prepare for marriage', Icons.favorite_border),
|
||||
const SizedBox(height: 12),
|
||||
_buildRelationshipOption(RelationshipStatus.married, 'Married', 'Fertility & intimacy', Icons.favorite),
|
||||
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _previousPage,
|
||||
style: OutlinedButton.styleFrom(foregroundColor: AppColors.sageGreen, side: BorderSide(color: AppColors.sageGreen)),
|
||||
child: const Text('Back'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: (_relationshipStatus != null && !_isNavigating) ? _nextPage : null,
|
||||
child: const Text('Continue'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRelationshipOption(RelationshipStatus status, String title, String subtitle, IconData icon) {
|
||||
final isSelected = _relationshipStatus == status;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _relationshipStatus = status),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.sageGreen.withOpacity(0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.sageGreen : AppColors.lightGray.withOpacity(0.5),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: isSelected ? AppColors.sageGreen : AppColors.warmGray),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: GoogleFonts.outfit(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.charcoal)),
|
||||
Text(subtitle, style: GoogleFonts.outfit(fontSize: 13, color: AppColors.warmGray)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isSelected) Icon(Icons.check_circle, color: AppColors.sageGreen),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFertilityGoalPage() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text('What\'s your goal?', style: GoogleFonts.outfit(fontSize: 28, fontWeight: FontWeight.w600, color: AppColors.charcoal)),
|
||||
const SizedBox(height: 32),
|
||||
_buildGoalOption(FertilityGoal.tryingToConceive, 'Trying to Conceive', 'Track fertile days', Icons.child_care_outlined),
|
||||
const SizedBox(height: 12),
|
||||
_buildGoalOption(FertilityGoal.tryingToAvoid, 'Natural Family Planning', 'Track fertility signs', Icons.calendar_today_outlined),
|
||||
const SizedBox(height: 12),
|
||||
_buildGoalOption(FertilityGoal.justTracking, 'Just Tracking', 'Monitor cycle health', Icons.insights_outlined),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _previousPage,
|
||||
style: OutlinedButton.styleFrom(foregroundColor: AppColors.sageGreen, side: BorderSide(color: AppColors.sageGreen)),
|
||||
child: const Text('Back'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: (_fertilityGoal != null && !_isNavigating) ? _nextPage : null,
|
||||
child: const Text('Continue'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGoalOption(FertilityGoal goal, String title, String subtitle, IconData icon) {
|
||||
final isSelected = _fertilityGoal == goal;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _fertilityGoal = goal),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.sageGreen.withOpacity(0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.sageGreen : AppColors.lightGray.withOpacity(0.5),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: isSelected ? AppColors.sageGreen : AppColors.warmGray),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: GoogleFonts.outfit(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.charcoal)),
|
||||
Text(subtitle, style: GoogleFonts.outfit(fontSize: 13, color: AppColors.warmGray)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isSelected) Icon(Icons.check_circle, color: AppColors.sageGreen),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCyclePage() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text('About your cycle', style: GoogleFonts.outfit(fontSize: 28, fontWeight: FontWeight.w600, color: AppColors.charcoal)),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
Text('Average cycle length', style: GoogleFonts.outfit(fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.charcoal)),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _averageCycleLength.toDouble(),
|
||||
min: 21,
|
||||
max: 40,
|
||||
divisions: 19,
|
||||
onChanged: (value) => setState(() => _averageCycleLength = value.round()),
|
||||
),
|
||||
),
|
||||
Text('$_averageCycleLength days', style: GoogleFonts.outfit(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.sageGreen)),
|
||||
],
|
||||
),
|
||||
|
||||
// Irregular Cycle Checkbox
|
||||
CheckboxListTile(
|
||||
title: Text('My cycles are irregular', style: GoogleFonts.outfit(fontSize: 14, color: AppColors.charcoal)),
|
||||
value: _isIrregularCycle,
|
||||
onChanged: (val) => setState(() => _isIrregularCycle = val ?? false),
|
||||
activeColor: AppColors.sageGreen,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
Text('Last period start date', style: GoogleFonts.outfit(fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.charcoal)),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _lastPeriodStart ?? DateTime.now(),
|
||||
firstDate: DateTime.now().subtract(const Duration(days: 60)),
|
||||
lastDate: DateTime.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: const ColorScheme.light(primary: AppColors.sageGreen, onPrimary: Colors.white, surface: Colors.white, onSurface: AppColors.charcoal),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (date != null) setState(() => _lastPeriodStart = date);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.lightGray.withOpacity(0.5))),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.calendar_today, color: AppColors.warmGray),
|
||||
const SizedBox(width: 12),
|
||||
Text(_lastPeriodStart != null ? "${_lastPeriodStart!.month}/${_lastPeriodStart!.day}/${_lastPeriodStart!.year}" : "Select Date", style: GoogleFonts.outfit(fontSize: 16, color: AppColors.charcoal)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _previousPage,
|
||||
style: OutlinedButton.styleFrom(foregroundColor: AppColors.sageGreen, side: BorderSide(color: AppColors.sageGreen)),
|
||||
child: const Text('Back'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: (_lastPeriodStart != null && !_isNavigating) ? _nextPage : null,
|
||||
child: const Text('Get Started'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
183
lib/screens/splash_screen.dart
Normal file
@@ -0,0 +1,183 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import 'onboarding/onboarding_screen.dart';
|
||||
import 'home/home_screen.dart';
|
||||
import '../models/user_profile.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../providers/user_provider.dart';
|
||||
import 'husband/husband_home_screen.dart';
|
||||
|
||||
class SplashScreen extends ConsumerStatefulWidget {
|
||||
const SplashScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SplashScreen> createState() => _SplashScreenState();
|
||||
}
|
||||
|
||||
class _SplashScreenState extends ConsumerState<SplashScreen> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
|
||||
),
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: const Interval(0.0, 0.5, curve: Curves.easeOutBack),
|
||||
),
|
||||
);
|
||||
|
||||
_controller.forward();
|
||||
|
||||
// Navigate after splash
|
||||
Future.delayed(const Duration(milliseconds: 2500), () {
|
||||
_navigateToNextScreen();
|
||||
});
|
||||
}
|
||||
|
||||
void _navigateToNextScreen() {
|
||||
final user = ref.read(userProfileProvider);
|
||||
final hasProfile = user != null;
|
||||
|
||||
Widget nextScreen;
|
||||
if (!hasProfile) {
|
||||
nextScreen = const OnboardingScreen();
|
||||
} else if (user.role == UserRole.husband) {
|
||||
nextScreen = const HusbandHomeScreen();
|
||||
} else {
|
||||
nextScreen = const HomeScreen();
|
||||
}
|
||||
|
||||
Navigator.of(context).pushReplacement(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => nextScreen,
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.cream,
|
||||
body: Center(
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// App Icon/Logo
|
||||
Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColors.blushPink,
|
||||
AppColors.rose.withOpacity(0.8),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.rose.withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.favorite_rounded,
|
||||
size: 60,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// App Name placeholder
|
||||
Text(
|
||||
'Period Tracker',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Tagline
|
||||
Text(
|
||||
'Faith-Centered Wellness',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.warmGray,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
// Scripture
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 48),
|
||||
child: Text(
|
||||
'"I praise you because I am\nfearfully and wonderfully made."',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'— Psalm 139:14',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
65
lib/services/cycle_service.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import '../models/user_profile.dart';
|
||||
import '../models/cycle_entry.dart';
|
||||
|
||||
class CycleService {
|
||||
/// Calculates the current cycle information based on user profile
|
||||
static Map<String, dynamic> calculateCycleInfo(UserProfile? user) {
|
||||
if (user?.lastPeriodStartDate == null) {
|
||||
return {
|
||||
'phase': CyclePhase.follicular,
|
||||
'dayOfCycle': 1,
|
||||
'daysUntilPeriod': user?.averageCycleLength ?? 28,
|
||||
'isPeriodExpected': false,
|
||||
};
|
||||
}
|
||||
|
||||
final lastPeriod = user!.lastPeriodStartDate!;
|
||||
final cycleLength = user.averageCycleLength;
|
||||
final now = DateTime.now();
|
||||
|
||||
// Normalize dates to midnight for accurate day counting
|
||||
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||
final startOfLastPeriod = DateTime(lastPeriod.year, lastPeriod.month, lastPeriod.day);
|
||||
|
||||
final daysSinceLastPeriod = startOfToday.difference(startOfLastPeriod).inDays + 1;
|
||||
|
||||
// Handle cases where last period was long ago (more than one cycle)
|
||||
final dayOfCycle = ((daysSinceLastPeriod - 1) % cycleLength) + 1;
|
||||
final daysUntilPeriod = cycleLength - dayOfCycle;
|
||||
|
||||
CyclePhase phase;
|
||||
if (dayOfCycle <= 5) {
|
||||
phase = CyclePhase.menstrual;
|
||||
} else if (dayOfCycle <= 13) {
|
||||
phase = CyclePhase.follicular;
|
||||
} else if (dayOfCycle <= 16) {
|
||||
phase = CyclePhase.ovulation;
|
||||
} else {
|
||||
phase = CyclePhase.luteal;
|
||||
}
|
||||
|
||||
return {
|
||||
'phase': phase,
|
||||
'dayOfCycle': dayOfCycle,
|
||||
'daysUntilPeriod': daysUntilPeriod,
|
||||
'isPeriodExpected': daysUntilPeriod <= 0 || dayOfCycle <= 5,
|
||||
};
|
||||
}
|
||||
|
||||
/// Format cycle day for display
|
||||
static String getDayOfCycleDisplay(int day) => 'Day $day';
|
||||
|
||||
/// Get phase description
|
||||
static String getPhaseDescription(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'Your body is resting and clearing. Be gentle with yourself.';
|
||||
case CyclePhase.follicular:
|
||||
return 'Energy and optimism are rising. A great time for new projects.';
|
||||
case CyclePhase.ovulation:
|
||||
return 'You are at your peak energy and fertility.';
|
||||
case CyclePhase.luteal:
|
||||
return 'Progesterone is rising. You may feel more introverted or sensitive.';
|
||||
}
|
||||
}
|
||||
}
|
||||
307
lib/theme/app_theme.dart
Normal file
@@ -0,0 +1,307 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
/// App color palette for wife's experience
|
||||
class AppColors {
|
||||
// Primary Colors (Wife's App)
|
||||
static const Color blushPink = Color(0xFFF8E1E7);
|
||||
static const Color rose = Color(0xFFE8A0B0);
|
||||
static const Color sageGreen = Color(0xFFA8C5A8);
|
||||
static const Color lavender = Color(0xFFD4C4E8);
|
||||
static const Color cream = Color(0xFFFDF8F5);
|
||||
static const Color softGold = Color(0xFFD4A574);
|
||||
|
||||
// Text Colors
|
||||
static const Color charcoal = Color(0xFF3D3D3D);
|
||||
static const Color warmGray = Color(0xFF7A7A7A);
|
||||
static const Color lightGray = Color(0xFFB8B8B8);
|
||||
|
||||
// Husband's App Colors
|
||||
static const Color navyBlue = Color(0xFF2C3E50);
|
||||
static const Color steelBlue = Color(0xFF5D7B93);
|
||||
static const Color warmCream = Color(0xFFF5F0E8);
|
||||
static const Color gold = Color(0xFFC9A961);
|
||||
static const Color softCoral = Color(0xFFE8B4A8);
|
||||
|
||||
// Phase Colors
|
||||
static const Color menstrualPhase = Color(0xFFE88A9E);
|
||||
static const Color follicularPhase = Color(0xFF8BC5A3);
|
||||
static const Color ovulationPhase = Color(0xFFB8A5D4);
|
||||
static const Color lutealPhase = Color(0xFF8BA5C5);
|
||||
|
||||
// Semantic Colors
|
||||
static const Color success = Color(0xFF7AB98A);
|
||||
static const Color warning = Color(0xFFE8C567);
|
||||
static const Color error = Color(0xFFE87B7B);
|
||||
static const Color info = Color(0xFF7BB8E8);
|
||||
}
|
||||
|
||||
/// App theme configuration
|
||||
class AppTheme {
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
|
||||
// Color Scheme
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: AppColors.sageGreen,
|
||||
secondary: AppColors.rose,
|
||||
tertiary: AppColors.lavender,
|
||||
surface: AppColors.cream,
|
||||
error: AppColors.error,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: AppColors.charcoal,
|
||||
),
|
||||
|
||||
// Scaffold
|
||||
scaffoldBackgroundColor: AppColors.cream,
|
||||
|
||||
// AppBar
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: AppColors.cream,
|
||||
foregroundColor: AppColors.charcoal,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
titleTextStyle: GoogleFonts.outfit(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
|
||||
// Text Theme
|
||||
textTheme: TextTheme(
|
||||
displayLarge: GoogleFonts.outfit(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
displayMedium: GoogleFonts.outfit(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
headlineLarge: GoogleFonts.outfit(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
headlineMedium: GoogleFonts.outfit(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
titleLarge: GoogleFonts.outfit(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
titleMedium: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
bodyLarge: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
bodyMedium: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
bodySmall: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
labelLarge: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
|
||||
// Card Theme
|
||||
cardTheme: CardTheme(
|
||||
color: Colors.white,
|
||||
elevation: 2,
|
||||
shadowColor: AppColors.charcoal.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
|
||||
// Button Themes
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.sageGreen,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.sageGreen,
|
||||
side: const BorderSide(color: AppColors.sageGreen, width: 1.5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.rose,
|
||||
textStyle: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Input Decoration
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.lightGray.withOpacity(0.5)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.lightGray.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.sageGreen, width: 2),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
hintStyle: GoogleFonts.outfit(
|
||||
color: AppColors.lightGray,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom Navigation
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: AppColors.sageGreen,
|
||||
unselectedItemColor: AppColors.warmGray,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 8,
|
||||
selectedLabelStyle: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
unselectedLabelStyle: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
|
||||
// Floating Action Button
|
||||
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||
backgroundColor: AppColors.sageGreen,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 4,
|
||||
),
|
||||
|
||||
// Slider Theme
|
||||
sliderTheme: SliderThemeData(
|
||||
activeTrackColor: AppColors.sageGreen,
|
||||
inactiveTrackColor: AppColors.lightGray.withOpacity(0.3),
|
||||
thumbColor: AppColors.sageGreen,
|
||||
overlayColor: AppColors.sageGreen.withOpacity(0.2),
|
||||
trackHeight: 4,
|
||||
),
|
||||
|
||||
// Divider
|
||||
dividerTheme: DividerThemeData(
|
||||
color: AppColors.lightGray.withOpacity(0.3),
|
||||
thickness: 1,
|
||||
space: 24,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: AppColors.sageGreen,
|
||||
secondary: AppColors.rose,
|
||||
tertiary: AppColors.lavender,
|
||||
surface: Color(0xFF1E1E1E),
|
||||
error: AppColors.error,
|
||||
),
|
||||
|
||||
scaffoldBackgroundColor: const Color(0xFF121212),
|
||||
|
||||
textTheme: TextTheme(
|
||||
displayLarge: GoogleFonts.outfit(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
bodyLarge: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.white,
|
||||
),
|
||||
bodyMedium: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
|
||||
cardTheme: CardTheme(
|
||||
color: const Color(0xFF1E1E1E),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Scripture text style
|
||||
TextStyle scriptureStyle(BuildContext context, {double? fontSize}) {
|
||||
return GoogleFonts.lora(
|
||||
fontSize: fontSize ?? 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
);
|
||||
}
|
||||
|
||||
/// Scripture reference style
|
||||
TextStyle scriptureRefStyle(BuildContext context) {
|
||||
return GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
);
|
||||
}
|
||||
195
lib/widgets/cycle_ring.dart
Normal file
@@ -0,0 +1,195 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'dart:math' as math;
|
||||
import '../theme/app_theme.dart';
|
||||
import '../models/cycle_entry.dart';
|
||||
|
||||
class CycleRing extends StatelessWidget {
|
||||
final int dayOfCycle;
|
||||
final int totalDays;
|
||||
final CyclePhase phase;
|
||||
|
||||
const CycleRing({
|
||||
super.key,
|
||||
required this.dayOfCycle,
|
||||
required this.totalDays,
|
||||
required this.phase,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final progress = dayOfCycle / totalDays;
|
||||
final daysUntilNextPeriod = totalDays - dayOfCycle;
|
||||
|
||||
return Container(
|
||||
width: 220,
|
||||
height: 220,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Background ring
|
||||
CustomPaint(
|
||||
size: const Size(220, 220),
|
||||
painter: _CycleRingPainter(
|
||||
progress: progress,
|
||||
phase: phase,
|
||||
),
|
||||
),
|
||||
|
||||
// Center content
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Day $dayOfCycle',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _getPhaseColor(phase).withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
phase.emoji,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
phase.label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _getPhaseColor(phase),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
daysUntilNextPeriod > 0
|
||||
? '$daysUntilNextPeriod days until period'
|
||||
: 'Period expected',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getPhaseColor(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return AppColors.menstrualPhase;
|
||||
case CyclePhase.follicular:
|
||||
return AppColors.follicularPhase;
|
||||
case CyclePhase.ovulation:
|
||||
return AppColors.ovulationPhase;
|
||||
case CyclePhase.luteal:
|
||||
return AppColors.lutealPhase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CycleRingPainter extends CustomPainter {
|
||||
final double progress;
|
||||
final CyclePhase phase;
|
||||
|
||||
_CycleRingPainter({required this.progress, required this.phase});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final radius = size.width / 2 - 15;
|
||||
const strokeWidth = 12.0;
|
||||
|
||||
// Background arc
|
||||
final bgPaint = Paint()
|
||||
..color = AppColors.lightGray.withOpacity(0.2)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = strokeWidth
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
canvas.drawCircle(center, radius, bgPaint);
|
||||
|
||||
// Progress arc
|
||||
final progressPaint = Paint()
|
||||
..shader = SweepGradient(
|
||||
startAngle: -math.pi / 2,
|
||||
endAngle: math.pi * 1.5,
|
||||
colors: _getGradientColors(phase),
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
).createShader(Rect.fromCircle(center: center, radius: radius))
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = strokeWidth
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(center: center, radius: radius),
|
||||
-math.pi / 2,
|
||||
2 * math.pi * progress,
|
||||
false,
|
||||
progressPaint,
|
||||
);
|
||||
|
||||
// Dot at current position
|
||||
final dotAngle = -math.pi / 2 + 2 * math.pi * progress;
|
||||
final dotX = center.dx + radius * math.cos(dotAngle);
|
||||
final dotY = center.dy + radius * math.sin(dotAngle);
|
||||
|
||||
final dotPaint = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final dotBorderPaint = Paint()
|
||||
..color = _getPhaseColor(phase)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3;
|
||||
|
||||
canvas.drawCircle(Offset(dotX, dotY), 8, dotPaint);
|
||||
canvas.drawCircle(Offset(dotX, dotY), 8, dotBorderPaint);
|
||||
}
|
||||
|
||||
List<Color> _getGradientColors(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return [AppColors.rose, AppColors.menstrualPhase, AppColors.blushPink];
|
||||
case CyclePhase.follicular:
|
||||
return [AppColors.sageGreen, AppColors.follicularPhase, AppColors.sageGreen.withOpacity(0.7)];
|
||||
case CyclePhase.ovulation:
|
||||
return [AppColors.lavender, AppColors.ovulationPhase, AppColors.rose];
|
||||
case CyclePhase.luteal:
|
||||
return [AppColors.lutealPhase, AppColors.lavender, AppColors.blushPink];
|
||||
}
|
||||
}
|
||||
|
||||
Color _getPhaseColor(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return AppColors.menstrualPhase;
|
||||
case CyclePhase.follicular:
|
||||
return AppColors.follicularPhase;
|
||||
case CyclePhase.ovulation:
|
||||
return AppColors.ovulationPhase;
|
||||
case CyclePhase.luteal:
|
||||
return AppColors.lutealPhase;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
||||
95
lib/widgets/quick_log_buttons.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import '../screens/log/log_screen.dart';
|
||||
|
||||
class QuickLogButtons extends StatelessWidget {
|
||||
const QuickLogButtons({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
_buildQuickButton(
|
||||
icon: Icons.water_drop_outlined,
|
||||
label: 'Period',
|
||||
color: AppColors.menstrualPhase,
|
||||
onTap: () => _navigateToLog(context),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildQuickButton(
|
||||
icon: Icons.emoji_emotions_outlined,
|
||||
label: 'Mood',
|
||||
color: AppColors.softGold,
|
||||
onTap: () => _navigateToLog(context),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildQuickButton(
|
||||
icon: Icons.flash_on_outlined,
|
||||
label: 'Energy',
|
||||
color: AppColors.follicularPhase,
|
||||
onTap: () => _navigateToLog(context),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildQuickButton(
|
||||
icon: Icons.healing_outlined,
|
||||
label: 'Symptoms',
|
||||
color: AppColors.lavender,
|
||||
onTap: () => _navigateToLog(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _navigateToLog(BuildContext context) {
|
||||
// Navigate to the Log tab (index 2) of HomeScreen if possible,
|
||||
// but since we are inside a tab, we can't easily switch the parent tab index without context. Using a provider or callback would be best.
|
||||
// For now, let's push the LogScreen as a new route for "Quick Log" feel.
|
||||
// Ideally we would switch the BottomNavBar index.
|
||||
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => const Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(0),
|
||||
child: SizedBox.shrink()
|
||||
),
|
||||
body: LogScreen()
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickButton({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required Color color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 24),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
label,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
134
lib/widgets/scripture_card.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import '../models/cycle_entry.dart';
|
||||
|
||||
class ScriptureCard extends StatelessWidget {
|
||||
final String verse;
|
||||
final String reference;
|
||||
final CyclePhase phase;
|
||||
|
||||
const ScriptureCard({
|
||||
super.key,
|
||||
required this.verse,
|
||||
required this.reference,
|
||||
required this.phase,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: _getGradientColors(phase),
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _getPhaseColor(phase).withOpacity(0.2),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Scripture icon
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.menu_book_outlined,
|
||||
size: 18,
|
||||
color: AppColors.charcoal.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Today\'s Verse',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.charcoal.withOpacity(0.7),
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Verse
|
||||
Text(
|
||||
'"$verse"',
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Reference
|
||||
Text(
|
||||
'— $reference',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Color> _getGradientColors(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return [
|
||||
AppColors.blushPink.withOpacity(0.6),
|
||||
AppColors.cream,
|
||||
];
|
||||
case CyclePhase.follicular:
|
||||
return [
|
||||
AppColors.sageGreen.withOpacity(0.3),
|
||||
AppColors.cream,
|
||||
];
|
||||
case CyclePhase.ovulation:
|
||||
return [
|
||||
AppColors.lavender.withOpacity(0.5),
|
||||
AppColors.cream,
|
||||
];
|
||||
case CyclePhase.luteal:
|
||||
return [
|
||||
AppColors.lutealPhase.withOpacity(0.3),
|
||||
AppColors.cream,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Color _getPhaseColor(CyclePhase phase) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return AppColors.menstrualPhase;
|
||||
case CyclePhase.follicular:
|
||||
return AppColors.follicularPhase;
|
||||
case CyclePhase.ovulation:
|
||||
return AppColors.ovulationPhase;
|
||||
case CyclePhase.luteal:
|
||||
return AppColors.lutealPhase;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
lib/widgets/tip_card.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import '../models/cycle_entry.dart';
|
||||
|
||||
class TipCard extends StatelessWidget {
|
||||
final CyclePhase phase;
|
||||
final bool isMarried;
|
||||
|
||||
const TipCard({
|
||||
super.key,
|
||||
required this.phase,
|
||||
required this.isMarried,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tip = _getTipForPhase(phase, isMarried);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.charcoal.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.sageGreen.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
color: AppColors.sageGreen,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Today\'s Tip',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.charcoal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
tip,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 13,
|
||||
color: AppColors.warmGray,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getTipForPhase(CyclePhase phase, bool isMarried) {
|
||||
switch (phase) {
|
||||
case CyclePhase.menstrual:
|
||||
return 'This is a time for rest. Honor your body with extra sleep, warm drinks, and gentle movement. God designed your body with wisdom.';
|
||||
case CyclePhase.follicular:
|
||||
return 'Your energy is rising! This is a great time to start new projects, exercise more intensely, and spend time in community.';
|
||||
case CyclePhase.ovulation:
|
||||
if (isMarried) {
|
||||
return 'This is your most fertile window. You may feel more social and energetic. Prioritize connection with your spouse.';
|
||||
}
|
||||
return 'You may feel more social and confident during this phase. It\'s a great time for important conversations and presentations.';
|
||||
case CyclePhase.luteal:
|
||||
return 'As you enter the luteal phase, focus on nourishing foods, adequate sleep, and stress management. Be gentle with yourself.';
|
||||
}
|
||||
}
|
||||
}
|
||||
1
linux/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
145
linux/CMakeLists.txt
Normal file
@@ -0,0 +1,145 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
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 "christian_period_tracker")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.faithapps.christian_period_tracker")
|
||||
|
||||
# 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)
|
||||
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME above,
|
||||
# not the value here, or `flutter run` will no longer work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
# 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()
|
||||
88
linux/flutter/CMakeLists.txt
Normal file
@@ -0,0 +1,88 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
11
linux/flutter/generated_plugin_registrant.cc
Normal file
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
}
|
||||
15
linux/flutter/generated_plugin_registrant.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
23
linux/flutter/generated_plugins.cmake
Normal file
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
6
linux/main.cc
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
124
linux/my_application.cc
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "christian_period_tracker");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "christian_period_tracker");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID,
|
||||
"flags", G_APPLICATION_NON_UNIQUE,
|
||||
nullptr));
|
||||
}
|
||||
18
linux/my_application.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
7
macos/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
1
macos/Flutter/Flutter-Debug.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
1
macos/Flutter/Flutter-Release.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
16
macos/Flutter/GeneratedPluginRegistrant.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import flutter_local_notifications
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
705
macos/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,705 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||
buildPhases = (
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Flutter Assemble";
|
||||
productName = FLX;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||
remoteInfo = FLX;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Bundle Framework";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* christian_period_tracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "christian_period_tracker.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||
);
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10E42044A3C60003C045 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33FAB671232836740065AC1E /* Runner */,
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10ED2044A3C60003C045 /* christian_period_tracker.app */,
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC11242044D66E0003C045 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||
);
|
||||
name = Resources;
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||
);
|
||||
path = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33FAB671232836740065AC1E /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||
33E51914231749380026EE4D /* Release.entitlements */,
|
||||
33CC11242044D66E0003C045 /* Resources */,
|
||||
33BA886A226E78AF003329D5 /* Configs */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* christian_period_tracker.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 33CC10EC2044A3C60003C045;
|
||||
};
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
33CC111A2044C6BA0003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
33CC10EC2044A3C60003C045 /* Runner */,
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */,
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C80D3294CF70F00263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||
);
|
||||
inputPaths = (
|
||||
Flutter/ephemeral/tripwire,
|
||||
);
|
||||
outputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C80D1294CF70F00263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC10EC2044A3C60003C045 /* Runner */;
|
||||
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
33CC10F52044A3C60003C045 /* Base */,
|
||||
);
|
||||
name = MainMenu.xib;
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/christian_period_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/christian_period_tracker";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/christian_period_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/christian_period_tracker";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.faithapps.christianPeriodTracker.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/christian_period_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/christian_period_tracker";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
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_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
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_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
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_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
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_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
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_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
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_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C80DB294CF71000263BE5 /* Debug */,
|
||||
331C80DC294CF71000263BE5 /* Release */,
|
||||
331C80DD294CF71000263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10F92044A3C60003C045 /* Debug */,
|
||||
33CC10FA2044A3C60003C045 /* Release */,
|
||||
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10FC2044A3C60003C045 /* Debug */,
|
||||
33CC10FD2044A3C60003C045 /* Release */,
|
||||
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC111C2044C6BA0003C045 /* Debug */,
|
||||
33CC111D2044C6BA0003C045 /* Release */,
|
||||
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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 = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "christian_period_tracker.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "christian_period_tracker.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
||||
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"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "christian_period_tracker.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 = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "christian_period_tracker.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
7
macos/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||