One-shot opus

This commit is contained in:
mplorentz 2026-04-20 14:39:49 -04:00
commit 27338de5f6
118 changed files with 6533 additions and 0 deletions

45
.gitignore vendored Normal file
View file

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

36
.metadata Normal file
View file

@ -0,0 +1,36 @@
# 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: "ac4e799d237041cf905519190471f657b657155a"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ac4e799d237041cf905519190471f657b657155a
base_revision: ac4e799d237041cf905519190471f657b657155a
- platform: android
create_revision: ac4e799d237041cf905519190471f657b657155a
base_revision: ac4e799d237041cf905519190471f657b657155a
- platform: ios
create_revision: ac4e799d237041cf905519190471f657b657155a
base_revision: ac4e799d237041cf905519190471f657b657155a
- platform: macos
create_revision: ac4e799d237041cf905519190471f657b657155a
base_revision: ac4e799d237041cf905519190471f657b657155a
# 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'

122
AGENTS.md Normal file
View file

@ -0,0 +1,122 @@
# Agent Guide
Preferences for AI agents working on this repository. Follow these strictly.
## Architecture
### State management: Riverpod only
- Use Riverpod for all state management and dependency injection.
- The app is wrapped in `ProviderScope` at the root (`lib/main.dart`).
- Use the right provider type:
- `Provider<T>` for services.
- `FutureProvider<T>` for async loads.
- `StreamProvider<T>` for reactive streams.
- `StreamProvider.family<T, Param>` for parameterized streams.
- `StateNotifierProvider<N, T>` for mutable, persisted state (see `trackerServiceProvider`).
### Services (business logic)
- All services are instance classes — never static.
- Accept dependencies via the constructor.
- Always expose via a `Provider`.
```dart
final myServiceProvider = Provider<MyService>((ref) {
return MyService(ref.read(repositoryProvider));
});
class MyService {
final MyRepository _repository;
MyService(this._repository);
}
```
### Repositories (data access)
Create a repository only when data access meets one of:
- Complex caching (in-memory + persistence).
- Stream management for reactive updates.
- Multiple specialized queries.
- Multiple consumers.
- Would otherwise be 100+ lines.
Otherwise, keep simple CRUD inline in the service. Never create thin repository
wrappers that just delegate to a service.
### Breaking circular dependencies
When two providers reference each other, add explicit types to break inference
cycles:
```dart
final Provider<ServiceA> serviceAProvider = Provider<ServiceA>((ref) {
final ServiceB b = ref.read(serviceBProvider);
return ServiceA(b);
});
```
## Code Quality (mandatory)
After every change, run in this order:
1. Codegen (if `@freezed` or `@GenerateMocks` changed):
`dart run build_runner build --delete-conflicting-outputs`
2. Format (always, especially after codegen): `dart format .`
3. Lints: use the ReadLints tool on modified files — must be clean.
4. Analyzer: `flutter analyze` — must show "No issues found!".
5. Tests: `flutter test` — 0 failures.
6. Goldens (if UI changed and golden test exists):
`flutter test <file> --update-goldens` and commit PNGs alongside the UI change.
Never commit unformatted code, lints, analyzer warnings, or UI changes without
corresponding golden updates. Never tell the user "it's clean" without running
the checks.
## Code Style
- Prefer `const` constructors everywhere possible.
- Use `final` for locals by default.
- Avoid `dynamic` — prefer explicit types.
- Use Freezed for immutable data models; avoid hand-rolled `==`/`hashCode`.
- No narrative comments. Don't write `// increment the counter`. Only comment
non-obvious intent, trade-offs, or constraints.
- Keep files focused; mirror `lib/` structure in `test/`.
## Testing
- Mock with `mockito` + `@GenerateMocks` + `build_runner`.
- Unit tests for services and models; golden tests for screens and widgets.
- Golden tests must run on macOS for consistent font rendering.
- Load bundled fonts in `test/flutter_test_config.dart`.
- Never skip failing tests to make CI pass — fix the root cause.
## UI
- Define a single theme in `lib/widgets/theme.dart` and reference design tokens
(`AppTokens`) — no inline magic colors.
- Keep `DESIGN_GUIDE.md` documenting color palette, typography, spacing, and
component patterns. Reference it before any UI work.
- Build reusable widgets (`TrackerTile`, `BookTile`, `ProgressBar`, etc.)
instead of repeating styling.
- Support both light and dark modes.
- Use `ListView.separated` for lists with dividers.
## Commits & PRs
- Commit messages focus on _why_, not _what_.
- Every commit on an open PR must be production-ready (format + analyze + test
+ lints + goldens).
- Commit generated files (freezed, mocks, goldens) alongside the source
changes that produced them.
- Don't open/push a PR until the full checklist passes.
## General Agent Behavior
- Read `README.md` and this file before starting work.
- Use todos for multi-step tasks; mark them complete promptly.
- Don't hide verification failures — report honestly and fix.
- Prefer editing existing files over creating new ones.
- Never create docs (`*.md`) proactively unless asked.
- Use specialized file tools (Read/Edit/Write) instead of `cat`/`sed`/`echo`.

113
DESIGN_GUIDE.md Normal file
View file

@ -0,0 +1,113 @@
# Design Guide
All UI code must reference tokens from `lib/widgets/theme.dart` (`AppTokens`)
rather than hard-coding values. Any new token belongs here first.
## Color
### Seed
- `AppTokens.seed` = `#3F51B5` (indigo).
Both the light and dark schemes are derived from this seed via
`ColorScheme.fromSeed`.
### Tracker palette
Each tracker chooses an accent color from `AppTokens.trackerPalette`. The
accent is applied to progress bars, the checked-chapter fill, and the header
stats. The palette (10 colors) is tuned to remain legible on both light and
dark surfaces:
| Color | Hex |
| ----- | --------- |
| Indigo | `#3F51B5` |
| Blue | `#1E88E5` |
| Teal | `#00897B` |
| Green | `#43A047` |
| Amber | `#FDD835` |
| Orange | `#FB8C00` |
| Red | `#E53935` |
| Pink | `#D81B60` |
| Purple | `#8E24AA` |
| Brown | `#6D4C41` |
### Usage rules
- **Never** use raw `Color(0x…)` or `Colors.red` outside of `theme.dart` or
the palette constant. Pull from `Theme.of(context).colorScheme` or pass the
tracker accent through.
- Inactive surfaces (unread chapters, progress bar track) use
`colorScheme.surfaceContainerHigh` / `surfaceContainerHighest`.
- Destructive actions use `colorScheme.error`.
## Typography
Use the Material 3 text theme. Reach for:
- `displaySmall` — hero progress percentages on the Stats screen.
- `headlineMedium` — progress percentage on the Tracker header.
- `titleLarge` / `titleMedium` — screen-level and card titles.
- `titleSmall` — list-item titles (e.g. book names).
- `bodyMedium` — body copy, stat values.
- `labelMedium` / `labelSmall` — compact counters, section metadata.
Weight overrides are allowed (`FontWeight.w500` / `w600` / `w700`) but font
family and sizes must come from the theme.
## Spacing
8pt grid, defined in `AppTokens`:
| Token | Value |
| ------------ | ----- |
| `spaceXS` | 4 |
| `spaceS` | 8 |
| `spaceM` | 12 |
| `spaceL` | 16 |
| `spaceXL` | 24 |
| `spaceXXL` | 32 |
Screen padding defaults to `spaceL`. Inner card padding defaults to `spaceL`.
Gaps between related widgets use `spaceS`–`spaceM`; gaps between distinct
sections use `spaceXL`.
## Radius
| Token | Value |
| ---------- | ----- |
| `radiusS` | 8 |
| `radiusM` | 12 |
| `radiusL` | 16 |
Cards: `radiusM`. Chapter cells: `radiusM`. Pill chips: `radiusL`. Buttons:
`radiusS`.
## Components
- **`TrackerTile`** — card on the home list. Shows dot + name + percent pill
+ progress bar.
- **`BookTile`** — list row on the Tracker screen. Shows leading status badge,
book name, inline progress bar, and `read / total` counter.
- **`ProgressBar`** — thin rounded linear indicator with a tracker accent
color on top of the `surfaceContainerHighest` track.
- **`ProgressPill`** — percent chip tinted with the accent color at 15% alpha.
- **Chapter cell** (`book_screen.dart`) — square tile, accent-filled when
read; tap toggles.
## Layout conventions
- Home, Tracker, and Stats screens each own a single `Scaffold` with a
contextual `AppBar`.
- Long scrollable lists always use `ListView.separated` with a thin
`outlineVariant` divider. Books within a testament are grouped under a
`_SectionHeader`.
- Destructive confirmations (delete, reset) use a standard `AlertDialog` with
an error-tinted confirm button.
## Theming
`AppTheme.light()` / `AppTheme.dark()` build from a Material 3
`ColorScheme.fromSeed`. Component themes (`AppBarTheme`, `CardThemeData`,
`ListTileThemeData`, `DividerThemeData`, `FilledButtonThemeData`,
`ProgressIndicatorThemeData`) are centralized so individual widgets stay
declarative.

64
README.md Normal file
View file

@ -0,0 +1,64 @@
# Bible Tracker
A Flutter app for tracking your Bible reading progress at your own pace.
Inspired by the "My Bible Tracker" Android app.
## Features
- Multiple trackers with custom names and colors (e.g. one per reading plan).
- Check off chapters as you read them — no schedules, no deadlines.
- Overall progress percentage, per-testament breakdowns, per-book progress.
- Start date is recorded automatically; see days reading in Stats.
- Long-press a tracker on the home screen to rename, recolor, reset, or delete.
- Mark an entire book as read (or clear) from the book's screen.
- Light and dark modes that follow system settings.
- Local-only data (SharedPreferences); no accounts, no network calls.
## Project layout
```
lib/
data/bible.dart # 66-book Bible structure (names, chapter counts)
models/tracker.dart # Freezed Tracker model (+ generated files)
services/
progress.dart # Pure progress calculations
tracker_service.dart # StateNotifier managing trackers
tracker_storage.dart # SharedPreferences-backed repository
widgets/
theme.dart # AppTokens + AppTheme (light/dark)
progress_bar.dart
tracker_tile.dart
book_tile.dart
screens/
home_screen.dart
create_tracker_screen.dart
tracker_screen.dart
book_screen.dart
stats_screen.dart
main.dart
test/ # Mirrors lib/ layout
DESIGN_GUIDE.md # Colors, typography, spacing, components
AGENTS.md # Contributor / AI-agent guide
```
## Getting started
```bash
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter run
```
## Verification checklist
Run after every change:
```bash
dart run build_runner build --delete-conflicting-outputs # if codegen changed
dart format .
flutter analyze
flutter test
```
Never commit unformatted code, analyzer warnings, or a broken test suite.
See `AGENTS.md` for full contribution rules.

14
analysis_options.yaml Normal file
View file

@ -0,0 +1,14 @@
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "**/*.mocks.dart"
linter:
rules:
- prefer_const_constructors
- prefer_const_declarations
- prefer_final_locals
- unawaited_futures

14
android/.gitignore vendored Normal file
View file

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

View file

@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.matt.bible_tracker"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.matt.bible_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.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View file

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

View file

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="bible_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>

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

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

View file

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

View file

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

24
android/build.gradle.kts Normal file
View file

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

View file

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

View file

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

View file

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

34
ios/.gitignore vendored Normal file
View file

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

View file

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

View file

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

View file

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

43
ios/Podfile Normal file
View file

@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View file

@ -0,0 +1,619 @@
// !$*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 = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = S2FZ788KSW;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.matt.bibleTracker;
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.matt.bibleTracker.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.matt.bibleTracker.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.matt.bibleTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = S2FZ788KSW;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.matt.bibleTracker;
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)";
DEVELOPMENT_TEAM = S2FZ788KSW;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.matt.bibleTracker;
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 */;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View file

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

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

49
ios/Runner/Info.plist Normal file
View 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>Bible 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>bible_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>

View file

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

View file

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

283
lib/data/bible.dart Normal file
View file

@ -0,0 +1,283 @@
enum Testament { old, newT }
class Book {
const Book({
required this.index,
required this.name,
required this.chapterCount,
required this.testament,
});
final int index;
final String name;
final int chapterCount;
final Testament testament;
}
class Bible {
const Bible._();
static const List<Book> books = <Book>[
Book(index: 0, name: 'Genesis', chapterCount: 50, testament: Testament.old),
Book(index: 1, name: 'Exodus', chapterCount: 40, testament: Testament.old),
Book(
index: 2,
name: 'Leviticus',
chapterCount: 27,
testament: Testament.old,
),
Book(index: 3, name: 'Numbers', chapterCount: 36, testament: Testament.old),
Book(
index: 4,
name: 'Deuteronomy',
chapterCount: 34,
testament: Testament.old,
),
Book(index: 5, name: 'Joshua', chapterCount: 24, testament: Testament.old),
Book(index: 6, name: 'Judges', chapterCount: 21, testament: Testament.old),
Book(index: 7, name: 'Ruth', chapterCount: 4, testament: Testament.old),
Book(
index: 8,
name: '1 Samuel',
chapterCount: 31,
testament: Testament.old,
),
Book(
index: 9,
name: '2 Samuel',
chapterCount: 24,
testament: Testament.old,
),
Book(
index: 10,
name: '1 Kings',
chapterCount: 22,
testament: Testament.old,
),
Book(
index: 11,
name: '2 Kings',
chapterCount: 25,
testament: Testament.old,
),
Book(
index: 12,
name: '1 Chronicles',
chapterCount: 29,
testament: Testament.old,
),
Book(
index: 13,
name: '2 Chronicles',
chapterCount: 36,
testament: Testament.old,
),
Book(index: 14, name: 'Ezra', chapterCount: 10, testament: Testament.old),
Book(
index: 15,
name: 'Nehemiah',
chapterCount: 13,
testament: Testament.old,
),
Book(index: 16, name: 'Esther', chapterCount: 10, testament: Testament.old),
Book(index: 17, name: 'Job', chapterCount: 42, testament: Testament.old),
Book(
index: 18,
name: 'Psalms',
chapterCount: 150,
testament: Testament.old,
),
Book(
index: 19,
name: 'Proverbs',
chapterCount: 31,
testament: Testament.old,
),
Book(
index: 20,
name: 'Ecclesiastes',
chapterCount: 12,
testament: Testament.old,
),
Book(
index: 21,
name: 'Song of Solomon',
chapterCount: 8,
testament: Testament.old,
),
Book(index: 22, name: 'Isaiah', chapterCount: 66, testament: Testament.old),
Book(
index: 23,
name: 'Jeremiah',
chapterCount: 52,
testament: Testament.old,
),
Book(
index: 24,
name: 'Lamentations',
chapterCount: 5,
testament: Testament.old,
),
Book(
index: 25,
name: 'Ezekiel',
chapterCount: 48,
testament: Testament.old,
),
Book(index: 26, name: 'Daniel', chapterCount: 12, testament: Testament.old),
Book(index: 27, name: 'Hosea', chapterCount: 14, testament: Testament.old),
Book(index: 28, name: 'Joel', chapterCount: 3, testament: Testament.old),
Book(index: 29, name: 'Amos', chapterCount: 9, testament: Testament.old),
Book(index: 30, name: 'Obadiah', chapterCount: 1, testament: Testament.old),
Book(index: 31, name: 'Jonah', chapterCount: 4, testament: Testament.old),
Book(index: 32, name: 'Micah', chapterCount: 7, testament: Testament.old),
Book(index: 33, name: 'Nahum', chapterCount: 3, testament: Testament.old),
Book(
index: 34,
name: 'Habakkuk',
chapterCount: 3,
testament: Testament.old,
),
Book(
index: 35,
name: 'Zephaniah',
chapterCount: 3,
testament: Testament.old,
),
Book(index: 36, name: 'Haggai', chapterCount: 2, testament: Testament.old),
Book(
index: 37,
name: 'Zechariah',
chapterCount: 14,
testament: Testament.old,
),
Book(index: 38, name: 'Malachi', chapterCount: 4, testament: Testament.old),
Book(
index: 39,
name: 'Matthew',
chapterCount: 28,
testament: Testament.newT,
),
Book(index: 40, name: 'Mark', chapterCount: 16, testament: Testament.newT),
Book(index: 41, name: 'Luke', chapterCount: 24, testament: Testament.newT),
Book(index: 42, name: 'John', chapterCount: 21, testament: Testament.newT),
Book(index: 43, name: 'Acts', chapterCount: 28, testament: Testament.newT),
Book(
index: 44,
name: 'Romans',
chapterCount: 16,
testament: Testament.newT,
),
Book(
index: 45,
name: '1 Corinthians',
chapterCount: 16,
testament: Testament.newT,
),
Book(
index: 46,
name: '2 Corinthians',
chapterCount: 13,
testament: Testament.newT,
),
Book(
index: 47,
name: 'Galatians',
chapterCount: 6,
testament: Testament.newT,
),
Book(
index: 48,
name: 'Ephesians',
chapterCount: 6,
testament: Testament.newT,
),
Book(
index: 49,
name: 'Philippians',
chapterCount: 4,
testament: Testament.newT,
),
Book(
index: 50,
name: 'Colossians',
chapterCount: 4,
testament: Testament.newT,
),
Book(
index: 51,
name: '1 Thessalonians',
chapterCount: 5,
testament: Testament.newT,
),
Book(
index: 52,
name: '2 Thessalonians',
chapterCount: 3,
testament: Testament.newT,
),
Book(
index: 53,
name: '1 Timothy',
chapterCount: 6,
testament: Testament.newT,
),
Book(
index: 54,
name: '2 Timothy',
chapterCount: 4,
testament: Testament.newT,
),
Book(index: 55, name: 'Titus', chapterCount: 3, testament: Testament.newT),
Book(
index: 56,
name: 'Philemon',
chapterCount: 1,
testament: Testament.newT,
),
Book(
index: 57,
name: 'Hebrews',
chapterCount: 13,
testament: Testament.newT,
),
Book(index: 58, name: 'James', chapterCount: 5, testament: Testament.newT),
Book(
index: 59,
name: '1 Peter',
chapterCount: 5,
testament: Testament.newT,
),
Book(
index: 60,
name: '2 Peter',
chapterCount: 3,
testament: Testament.newT,
),
Book(index: 61, name: '1 John', chapterCount: 5, testament: Testament.newT),
Book(index: 62, name: '2 John', chapterCount: 1, testament: Testament.newT),
Book(index: 63, name: '3 John', chapterCount: 1, testament: Testament.newT),
Book(index: 64, name: 'Jude', chapterCount: 1, testament: Testament.newT),
Book(
index: 65,
name: 'Revelation',
chapterCount: 22,
testament: Testament.newT,
),
];
static int get totalChapters =>
books.fold<int>(0, (int sum, Book book) => sum + book.chapterCount);
static int get totalBooks => books.length;
static int totalChaptersIn(Testament testament) => books
.where((Book book) => book.testament == testament)
.fold<int>(0, (int sum, Book book) => sum + book.chapterCount);
static int totalBooksIn(Testament testament) =>
books.where((Book book) => book.testament == testament).length;
/// Chapter key used to identify a chapter in persisted state.
static String chapterKey(int bookIndex, int chapter) => '$bookIndex:$chapter';
}

34
lib/main.dart Normal file
View file

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'screens/home_screen.dart';
import 'services/tracker_storage.dart';
import 'widgets/theme.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final SharedPreferences prefs = await SharedPreferences.getInstance();
runApp(
ProviderScope(
overrides: <Override>[sharedPreferencesProvider.overrideWithValue(prefs)],
child: const BibleTrackerApp(),
),
);
}
class BibleTrackerApp extends StatelessWidget {
const BibleTrackerApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Bible Tracker',
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.system,
home: const HomeScreen(),
);
}
}

18
lib/models/tracker.dart Normal file
View file

@ -0,0 +1,18 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'tracker.freezed.dart';
part 'tracker.g.dart';
@freezed
class Tracker with _$Tracker {
const factory Tracker({
required String id,
required String name,
required int colorValue,
required DateTime startDate,
@Default(<String>{}) Set<String> readChapters,
}) = _Tracker;
factory Tracker.fromJson(Map<String, dynamic> json) =>
_$TrackerFromJson(json);
}

View file

@ -0,0 +1,272 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'tracker.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
Tracker _$TrackerFromJson(Map<String, dynamic> json) {
return _Tracker.fromJson(json);
}
/// @nodoc
mixin _$Tracker {
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
int get colorValue => throw _privateConstructorUsedError;
DateTime get startDate => throw _privateConstructorUsedError;
Set<String> get readChapters => throw _privateConstructorUsedError;
/// Serializes this Tracker to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of Tracker
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TrackerCopyWith<Tracker> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TrackerCopyWith<$Res> {
factory $TrackerCopyWith(Tracker value, $Res Function(Tracker) then) =
_$TrackerCopyWithImpl<$Res, Tracker>;
@useResult
$Res call({
String id,
String name,
int colorValue,
DateTime startDate,
Set<String> readChapters,
});
}
/// @nodoc
class _$TrackerCopyWithImpl<$Res, $Val extends Tracker>
implements $TrackerCopyWith<$Res> {
_$TrackerCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Tracker
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? colorValue = null,
Object? startDate = null,
Object? readChapters = null,
}) {
return _then(
_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
colorValue: null == colorValue
? _value.colorValue
: colorValue // ignore: cast_nullable_to_non_nullable
as int,
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
readChapters: null == readChapters
? _value.readChapters
: readChapters // ignore: cast_nullable_to_non_nullable
as Set<String>,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$TrackerImplCopyWith<$Res> implements $TrackerCopyWith<$Res> {
factory _$$TrackerImplCopyWith(
_$TrackerImpl value,
$Res Function(_$TrackerImpl) then,
) = __$$TrackerImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
String id,
String name,
int colorValue,
DateTime startDate,
Set<String> readChapters,
});
}
/// @nodoc
class __$$TrackerImplCopyWithImpl<$Res>
extends _$TrackerCopyWithImpl<$Res, _$TrackerImpl>
implements _$$TrackerImplCopyWith<$Res> {
__$$TrackerImplCopyWithImpl(
_$TrackerImpl _value,
$Res Function(_$TrackerImpl) _then,
) : super(_value, _then);
/// Create a copy of Tracker
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? colorValue = null,
Object? startDate = null,
Object? readChapters = null,
}) {
return _then(
_$TrackerImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
colorValue: null == colorValue
? _value.colorValue
: colorValue // ignore: cast_nullable_to_non_nullable
as int,
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
readChapters: null == readChapters
? _value._readChapters
: readChapters // ignore: cast_nullable_to_non_nullable
as Set<String>,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$TrackerImpl implements _Tracker {
const _$TrackerImpl({
required this.id,
required this.name,
required this.colorValue,
required this.startDate,
final Set<String> readChapters = const <String>{},
}) : _readChapters = readChapters;
factory _$TrackerImpl.fromJson(Map<String, dynamic> json) =>
_$$TrackerImplFromJson(json);
@override
final String id;
@override
final String name;
@override
final int colorValue;
@override
final DateTime startDate;
final Set<String> _readChapters;
@override
@JsonKey()
Set<String> get readChapters {
if (_readChapters is EqualUnmodifiableSetView) return _readChapters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableSetView(_readChapters);
}
@override
String toString() {
return 'Tracker(id: $id, name: $name, colorValue: $colorValue, startDate: $startDate, readChapters: $readChapters)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TrackerImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.colorValue, colorValue) ||
other.colorValue == colorValue) &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
const DeepCollectionEquality().equals(
other._readChapters,
_readChapters,
));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
name,
colorValue,
startDate,
const DeepCollectionEquality().hash(_readChapters),
);
/// Create a copy of Tracker
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TrackerImplCopyWith<_$TrackerImpl> get copyWith =>
__$$TrackerImplCopyWithImpl<_$TrackerImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TrackerImplToJson(this);
}
}
abstract class _Tracker implements Tracker {
const factory _Tracker({
required final String id,
required final String name,
required final int colorValue,
required final DateTime startDate,
final Set<String> readChapters,
}) = _$TrackerImpl;
factory _Tracker.fromJson(Map<String, dynamic> json) = _$TrackerImpl.fromJson;
@override
String get id;
@override
String get name;
@override
int get colorValue;
@override
DateTime get startDate;
@override
Set<String> get readChapters;
/// Create a copy of Tracker
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TrackerImplCopyWith<_$TrackerImpl> get copyWith =>
throw _privateConstructorUsedError;
}

29
lib/models/tracker.g.dart Normal file
View file

@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tracker.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TrackerImpl _$$TrackerImplFromJson(Map<String, dynamic> json) =>
_$TrackerImpl(
id: json['id'] as String,
name: json['name'] as String,
colorValue: (json['colorValue'] as num).toInt(),
startDate: DateTime.parse(json['startDate'] as String),
readChapters:
(json['readChapters'] as List<dynamic>?)
?.map((e) => e as String)
.toSet() ??
const <String>{},
);
Map<String, dynamic> _$$TrackerImplToJson(_$TrackerImpl instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'colorValue': instance.colorValue,
'startDate': instance.startDate.toIso8601String(),
'readChapters': instance.readChapters.toList(),
};

View file

@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/bible.dart';
import '../models/tracker.dart';
import '../services/progress.dart';
import '../services/tracker_service.dart';
import '../widgets/progress_bar.dart';
import '../widgets/theme.dart';
class BookScreen extends ConsumerWidget {
const BookScreen({
super.key,
required this.trackerId,
required this.bookIndex,
});
final String trackerId;
final int bookIndex;
@override
Widget build(BuildContext context, WidgetRef ref) {
final Tracker? tracker = ref.watch(trackerByIdProvider(trackerId));
if (tracker == null) {
return Scaffold(
appBar: AppBar(),
body: const Center(child: Text('Tracker not found')),
);
}
final Book book = Bible.books[bookIndex];
final Color accent = Color(tracker.colorValue);
final TrackerProgress progress = TrackerProgress(tracker);
final int chaptersRead = progress.chaptersReadIn(book);
final bool allRead = chaptersRead == book.chapterCount;
return Scaffold(
appBar: AppBar(
title: Text(book.name),
actions: <Widget>[
TextButton(
onPressed: () => ref
.read(trackerServiceProvider.notifier)
.setBookRead(trackerId, bookIndex, read: !allRead),
child: Text(allRead ? 'Clear book' : 'Mark all'),
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'$chaptersRead of ${book.chapterCount} chapters',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: AppTokens.spaceM),
ProgressBar(
value: progress.fractionOf(book),
color: accent,
height: 8,
),
],
),
),
const Divider(),
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(AppTokens.spaceL),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
mainAxisSpacing: AppTokens.spaceM,
crossAxisSpacing: AppTokens.spaceM,
childAspectRatio: 1,
),
itemCount: book.chapterCount,
itemBuilder: (BuildContext context, int index) {
final int chapter = index + 1;
final bool read = tracker.readChapters.contains(
Bible.chapterKey(bookIndex, chapter),
);
return _ChapterCell(
chapter: chapter,
read: read,
accent: accent,
onTap: () => ref
.read(trackerServiceProvider.notifier)
.toggleChapter(trackerId, bookIndex, chapter),
);
},
),
),
],
),
);
}
}
class _ChapterCell extends StatelessWidget {
const _ChapterCell({
required this.chapter,
required this.read,
required this.accent,
required this.onTap,
});
final int chapter;
final bool read;
final Color accent;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
color: read ? accent : Theme.of(context).colorScheme.surfaceContainerHigh,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppTokens.radiusM),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppTokens.radiusM),
child: Center(
child: Text(
'$chapter',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: read
? Colors.white
: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
}

View file

@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/tracker.dart';
import '../services/tracker_service.dart';
import '../widgets/theme.dart';
class CreateTrackerScreen extends ConsumerStatefulWidget {
const CreateTrackerScreen({super.key, this.existing});
final Tracker? existing;
@override
ConsumerState<CreateTrackerScreen> createState() =>
_CreateTrackerScreenState();
}
class _CreateTrackerScreenState extends ConsumerState<CreateTrackerScreen> {
late final TextEditingController _nameController;
late Color _color;
bool _saving = false;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.existing?.name ?? '');
_color = widget.existing != null
? Color(widget.existing!.colorValue)
: AppTokens.trackerPalette.first;
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
Future<void> _save() async {
final String name = _nameController.text.trim();
if (name.isEmpty) return;
setState(() => _saving = true);
final TrackerService service = ref.read(trackerServiceProvider.notifier);
if (widget.existing == null) {
await service.create(name: name, color: _color);
} else {
await service.updateMeta(widget.existing!.id, name: name, color: _color);
}
if (!mounted) return;
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
final bool isEditing = widget.existing != null;
return Scaffold(
appBar: AppBar(title: Text(isEditing ? 'Edit Tracker' : 'New Tracker')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
hintText: 'e.g. My First Read-Through',
border: OutlineInputBorder(),
),
textInputAction: TextInputAction.done,
autofocus: !isEditing,
),
const SizedBox(height: AppTokens.spaceXL),
Text('Color', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: AppTokens.spaceM),
Wrap(
spacing: AppTokens.spaceM,
runSpacing: AppTokens.spaceM,
children: <Widget>[
for (final Color option in AppTokens.trackerPalette)
_ColorSwatch(
color: option,
selected: option.toARGB32() == _color.toARGB32(),
onTap: () => setState(() => _color = option),
),
],
),
const SizedBox(height: AppTokens.spaceXXL),
FilledButton(
onPressed: _saving ? null : _save,
style: FilledButton.styleFrom(backgroundColor: _color),
child: Text(isEditing ? 'Save Changes' : 'Create Tracker'),
),
],
),
),
);
}
}
class _ColorSwatch extends StatelessWidget {
const _ColorSwatch({
required this.color,
required this.selected,
required this.onTap,
});
final Color color;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkResponse(
onTap: onTap,
radius: 32,
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: selected
? Theme.of(context).colorScheme.onSurface
: Colors.transparent,
width: 3,
),
),
child: selected
? const Icon(Icons.check, color: Colors.white, size: 20)
: null,
),
);
}
}

View file

@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/tracker.dart';
import '../services/tracker_service.dart';
import '../widgets/theme.dart';
import '../widgets/tracker_tile.dart';
import 'create_tracker_screen.dart';
import 'tracker_screen.dart';
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final AsyncValue<List<Tracker>> state = ref.watch(trackerServiceProvider);
return Scaffold(
appBar: AppBar(title: const Text('My Bible Tracker')),
body: state.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (Object error, StackTrace stack) =>
Center(child: Text('Something went wrong:\n$error')),
data: (List<Tracker> trackers) {
if (trackers.isEmpty) {
return const _EmptyState();
}
return ListView.separated(
padding: const EdgeInsets.all(AppTokens.spaceL),
itemCount: trackers.length,
separatorBuilder: (_, __) =>
const SizedBox(height: AppTokens.spaceM),
itemBuilder: (BuildContext context, int index) {
final Tracker tracker = trackers[index];
return TrackerTile(
tracker: tracker,
onTap: () => Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) => TrackerScreen(trackerId: tracker.id),
),
),
onLongPress: () => _showTrackerMenu(context, ref, tracker),
);
},
);
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => Navigator.of(context).push<void>(
MaterialPageRoute<void>(builder: (_) => const CreateTrackerScreen()),
),
icon: const Icon(Icons.add),
label: const Text('New Tracker'),
),
);
}
Future<void> _showTrackerMenu(
BuildContext context,
WidgetRef ref,
Tracker tracker,
) async {
final _TrackerMenuAction? action =
await showModalBottomSheet<_TrackerMenuAction>(
context: context,
showDragHandle: true,
builder: (BuildContext context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.edit_outlined),
title: const Text('Rename / recolor'),
onTap: () =>
Navigator.of(context).pop(_TrackerMenuAction.edit),
),
ListTile(
leading: const Icon(Icons.refresh),
title: const Text('Reset progress'),
onTap: () =>
Navigator.of(context).pop(_TrackerMenuAction.reset),
),
ListTile(
leading: const Icon(Icons.delete_outline),
iconColor: Theme.of(context).colorScheme.error,
textColor: Theme.of(context).colorScheme.error,
title: const Text('Delete'),
onTap: () =>
Navigator.of(context).pop(_TrackerMenuAction.delete),
),
const SizedBox(height: AppTokens.spaceS),
],
),
);
},
);
if (action == null || !context.mounted) return;
switch (action) {
case _TrackerMenuAction.edit:
await Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) => CreateTrackerScreen(existing: tracker),
),
);
case _TrackerMenuAction.reset:
await _confirmAndRun(
context,
title: 'Reset progress?',
message:
'All checked chapters for "${tracker.name}" will be cleared.',
destructive: true,
run: () =>
ref.read(trackerServiceProvider.notifier).reset(tracker.id),
);
case _TrackerMenuAction.delete:
await _confirmAndRun(
context,
title: 'Delete tracker?',
message: '"${tracker.name}" will be removed permanently.',
destructive: true,
run: () =>
ref.read(trackerServiceProvider.notifier).delete(tracker.id),
);
}
}
Future<void> _confirmAndRun(
BuildContext context, {
required String title,
required String message,
required bool destructive,
required Future<void> Function() run,
}) async {
final bool? confirmed = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
FilledButton(
style: destructive
? FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
)
: null,
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Confirm'),
),
],
),
);
if (confirmed ?? false) {
await run();
}
}
}
enum _TrackerMenuAction { edit, reset, delete }
class _EmptyState extends StatelessWidget {
const _EmptyState();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(AppTokens.spaceXXL),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.menu_book_outlined,
size: 72,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: AppTokens.spaceL),
Text(
'Start your first tracker',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: AppTokens.spaceS),
Text(
'Create a tracker to mark the chapters you have read. '
'Read at your own pace — there are no deadlines.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,220 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/bible.dart';
import '../models/tracker.dart';
import '../services/progress.dart';
import '../services/tracker_service.dart';
import '../widgets/progress_bar.dart';
import '../widgets/theme.dart';
class StatsScreen extends ConsumerWidget {
const StatsScreen({super.key, required this.trackerId});
final String trackerId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final Tracker? tracker = ref.watch(trackerByIdProvider(trackerId));
if (tracker == null) {
return Scaffold(
appBar: AppBar(),
body: const Center(child: Text('Tracker not found')),
);
}
final Color accent = Color(tracker.colorValue);
final TrackerProgress progress = TrackerProgress(tracker);
final int days = DateTime.now().difference(tracker.startDate).inDays;
return Scaffold(
appBar: AppBar(title: const Text('Stats')),
body: ListView(
padding: const EdgeInsets.all(AppTokens.spaceL),
children: <Widget>[
_OverallCard(tracker: tracker, accent: accent, progress: progress),
const SizedBox(height: AppTokens.spaceL),
_SectionCard(
title: 'Old Testament',
chapters: progress.chaptersReadInTestament(Testament.old),
chaptersTotal: Bible.totalChaptersIn(Testament.old),
books: progress.booksCompleted(Testament.old),
booksTotal: Bible.totalBooksIn(Testament.old),
fraction: progress.testamentFraction(Testament.old),
accent: accent,
),
const SizedBox(height: AppTokens.spaceL),
_SectionCard(
title: 'New Testament',
chapters: progress.chaptersReadInTestament(Testament.newT),
chaptersTotal: Bible.totalChaptersIn(Testament.newT),
books: progress.booksCompleted(Testament.newT),
booksTotal: Bible.totalBooksIn(Testament.newT),
fraction: progress.testamentFraction(Testament.newT),
accent: accent,
),
const SizedBox(height: AppTokens.spaceL),
Card(
child: Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'Since you started',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: AppTokens.spaceM),
_StatRow(
label: 'Start date',
value: _formatDate(tracker.startDate),
),
_StatRow(label: 'Days reading', value: '$days'),
_StatRow(
label: 'Chapters read',
value: '${progress.totalRead} / ${Bible.totalChapters}',
),
_StatRow(
label: 'Books completed',
value: '${progress.booksCompleted()} / ${Bible.totalBooks}',
),
],
),
),
),
],
),
);
}
static String _formatDate(DateTime date) {
const List<String> months = <String>[
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
return '${months[date.month - 1]} ${date.day}, ${date.year}';
}
}
class _OverallCard extends StatelessWidget {
const _OverallCard({
required this.tracker,
required this.accent,
required this.progress,
});
final Tracker tracker;
final Color accent;
final TrackerProgress progress;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(tracker.name, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: AppTokens.spaceS),
Text(
'${(progress.overall * 100).toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.displaySmall?.copyWith(
color: accent,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppTokens.spaceM),
ProgressBar(value: progress.overall, color: accent, height: 10),
],
),
),
);
}
}
class _SectionCard extends StatelessWidget {
const _SectionCard({
required this.title,
required this.chapters,
required this.chaptersTotal,
required this.books,
required this.booksTotal,
required this.fraction,
required this.accent,
});
final String title;
final int chapters;
final int chaptersTotal;
final int books;
final int booksTotal;
final double fraction;
final Color accent;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(title, style: Theme.of(context).textTheme.titleMedium),
ProgressPill(fraction: fraction, color: accent),
],
),
const SizedBox(height: AppTokens.spaceM),
ProgressBar(value: fraction, color: accent),
const SizedBox(height: AppTokens.spaceM),
_StatRow(label: 'Chapters', value: '$chapters / $chaptersTotal'),
_StatRow(label: 'Books', value: '$books / $booksTotal'),
],
),
),
);
}
}
class _StatRow extends StatelessWidget {
const _StatRow({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppTokens.spaceXS),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Text(
value,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
),
],
),
);
}
}

View file

@ -0,0 +1,259 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/bible.dart';
import '../models/tracker.dart';
import '../services/progress.dart';
import '../services/tracker_service.dart';
import '../widgets/book_tile.dart';
import '../widgets/progress_bar.dart';
import '../widgets/theme.dart';
import 'book_screen.dart';
import 'stats_screen.dart';
class TrackerScreen extends ConsumerWidget {
const TrackerScreen({super.key, required this.trackerId});
final String trackerId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final Tracker? tracker = ref.watch(trackerByIdProvider(trackerId));
if (tracker == null) {
return Scaffold(
appBar: AppBar(),
body: const Center(child: Text('Tracker not found')),
);
}
final Color accent = Color(tracker.colorValue);
final TrackerProgress progress = TrackerProgress(tracker);
return Scaffold(
appBar: AppBar(
title: Text(tracker.name),
actions: <Widget>[
IconButton(
tooltip: 'Stats',
icon: const Icon(Icons.insert_chart_outlined),
onPressed: () => Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) => StatsScreen(trackerId: tracker.id),
),
),
),
],
),
body: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: _HeaderCard(
tracker: tracker,
accent: accent,
progress: progress,
),
),
SliverToBoxAdapter(
child: _SectionHeader(
title: 'Old Testament',
chaptersRead: progress.chaptersReadInTestament(Testament.old),
total: Bible.totalChaptersIn(Testament.old),
),
),
_BooksSliver(
tracker: tracker,
accent: accent,
testament: Testament.old,
),
SliverToBoxAdapter(
child: _SectionHeader(
title: 'New Testament',
chaptersRead: progress.chaptersReadInTestament(Testament.newT),
total: Bible.totalChaptersIn(Testament.newT),
),
),
_BooksSliver(
tracker: tracker,
accent: accent,
testament: Testament.newT,
),
const SliverToBoxAdapter(child: SizedBox(height: AppTokens.spaceXL)),
],
),
);
}
}
class _HeaderCard extends StatelessWidget {
const _HeaderCard({
required this.tracker,
required this.accent,
required this.progress,
});
final Tracker tracker;
final Color accent;
final TrackerProgress progress;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Card(
child: Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'${(progress.overall * 100).toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.headlineMedium
?.copyWith(
color: accent,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppTokens.spaceXS),
Text(
'${progress.totalRead} / ${Bible.totalChapters} chapters',
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text(
'Started',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: AppTokens.spaceXS),
Text(
_formatDate(tracker.startDate),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
],
),
],
),
const SizedBox(height: AppTokens.spaceL),
ProgressBar(value: progress.overall, color: accent, height: 8),
],
),
),
),
);
}
static String _formatDate(DateTime date) {
const List<String> months = <String>[
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
return '${months[date.month - 1]} ${date.day}, ${date.year}';
}
}
class _SectionHeader extends StatelessWidget {
const _SectionHeader({
required this.title,
required this.chaptersRead,
required this.total,
});
final String title;
final int chaptersRead;
final int total;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(
AppTokens.spaceL,
AppTokens.spaceL,
AppTokens.spaceL,
AppTokens.spaceS,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
title,
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
Text(
'$chaptersRead / $total',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}
class _BooksSliver extends ConsumerWidget {
const _BooksSliver({
required this.tracker,
required this.accent,
required this.testament,
});
final Tracker tracker;
final Color accent;
final Testament testament;
@override
Widget build(BuildContext context, WidgetRef ref) {
final List<Book> books = Bible.books
.where((Book book) => book.testament == testament)
.toList(growable: false);
final TrackerProgress progress = TrackerProgress(tracker);
return SliverList.separated(
itemCount: books.length,
separatorBuilder: (_, __) => const Divider(indent: AppTokens.spaceXL),
itemBuilder: (BuildContext context, int index) {
final Book book = books[index];
return BookTile(
book: book,
chaptersRead: progress.chaptersReadIn(book),
accent: accent,
onTap: () => Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) =>
BookScreen(trackerId: tracker.id, bookIndex: book.index),
),
),
);
},
);
}
}

View file

@ -0,0 +1,56 @@
import '../data/bible.dart';
import '../models/tracker.dart';
/// Pure progress calculations over a [Tracker].
///
/// Kept free of Flutter imports so it can be covered by plain unit tests.
class TrackerProgress {
TrackerProgress(this.tracker);
final Tracker tracker;
int chaptersReadIn(Book book) {
int count = 0;
for (int chapter = 1; chapter <= book.chapterCount; chapter++) {
if (tracker.readChapters.contains(
Bible.chapterKey(book.index, chapter),
)) {
count++;
}
}
return count;
}
double fractionOf(Book book) {
if (book.chapterCount == 0) return 0;
return chaptersReadIn(book) / book.chapterCount;
}
bool isBookComplete(Book book) =>
chaptersReadIn(book) == book.chapterCount && book.chapterCount > 0;
bool isBookStarted(Book book) => chaptersReadIn(book) > 0;
int get totalRead => tracker.readChapters.length;
double get overall =>
Bible.totalChapters == 0 ? 0 : totalRead / Bible.totalChapters;
int booksCompleted([Testament? testament]) => Bible.books
.where(
(Book book) =>
(testament == null || book.testament == testament) &&
isBookComplete(book),
)
.length;
int chaptersReadInTestament(Testament testament) => Bible.books
.where((Book book) => book.testament == testament)
.fold<int>(0, (int sum, Book book) => sum + chaptersReadIn(book));
double testamentFraction(Testament testament) {
final int total = Bible.totalChaptersIn(testament);
if (total == 0) return 0;
return chaptersReadInTestament(testament) / total;
}
}

View file

@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';
import '../data/bible.dart';
import '../models/tracker.dart';
import 'tracker_storage.dart';
/// Manages the list of [Tracker]s and exposes them as state to the UI.
///
/// Kept as a plain service (no repository) because storage access is
/// simple read/write-all and there are no complex queries or caches.
class TrackerService extends StateNotifier<AsyncValue<List<Tracker>>> {
TrackerService(this._storage, {Uuid? uuid})
: _uuid = uuid ?? const Uuid(),
super(const AsyncValue<List<Tracker>>.loading()) {
unawaitedLoad();
}
final TrackerStorage _storage;
final Uuid _uuid;
@visibleForTesting
Future<void> unawaitedLoad() => _load();
Future<void> _load() async {
try {
final List<Tracker> trackers = await _storage.loadAll();
state = AsyncValue<List<Tracker>>.data(trackers);
} catch (error, stack) {
state = AsyncValue<List<Tracker>>.error(error, stack);
}
}
Future<void> _persist(List<Tracker> trackers) async {
state = AsyncValue<List<Tracker>>.data(trackers);
await _storage.saveAll(trackers);
}
Future<Tracker> create({required String name, required Color color}) async {
final Tracker tracker = Tracker(
id: _uuid.v4(),
name: name,
// ignore: deprecated_member_use
colorValue: color.value,
startDate: DateTime.now(),
);
final List<Tracker> next = <Tracker>[
...state.value ?? const <Tracker>[],
tracker,
];
await _persist(next);
return tracker;
}
Future<void> updateMeta(String id, {String? name, Color? color}) async {
final List<Tracker> current = state.value ?? const <Tracker>[];
final List<Tracker> next = current
.map(
(Tracker tracker) => tracker.id == id
? tracker.copyWith(
name: name ?? tracker.name,
// ignore: deprecated_member_use
colorValue: color?.value ?? tracker.colorValue,
)
: tracker,
)
.toList(growable: false);
await _persist(next);
}
Future<void> delete(String id) async {
final List<Tracker> current = state.value ?? const <Tracker>[];
final List<Tracker> next = current
.where((Tracker tracker) => tracker.id != id)
.toList(growable: false);
await _persist(next);
}
Future<void> toggleChapter(String id, int bookIndex, int chapter) async {
final String key = Bible.chapterKey(bookIndex, chapter);
final List<Tracker> current = state.value ?? const <Tracker>[];
final List<Tracker> next = current
.map((Tracker tracker) {
if (tracker.id != id) return tracker;
final Set<String> updated = <String>{...tracker.readChapters};
if (!updated.remove(key)) {
updated.add(key);
}
return tracker.copyWith(readChapters: updated);
})
.toList(growable: false);
await _persist(next);
}
Future<void> setBookRead(
String id,
int bookIndex, {
required bool read,
}) async {
final Book book = Bible.books[bookIndex];
final List<Tracker> current = state.value ?? const <Tracker>[];
final List<Tracker> next = current
.map((Tracker tracker) {
if (tracker.id != id) return tracker;
final Set<String> updated = <String>{...tracker.readChapters};
for (int chapter = 1; chapter <= book.chapterCount; chapter++) {
final String key = Bible.chapterKey(bookIndex, chapter);
if (read) {
updated.add(key);
} else {
updated.remove(key);
}
}
return tracker.copyWith(readChapters: updated);
})
.toList(growable: false);
await _persist(next);
}
Future<void> reset(String id) async {
final List<Tracker> current = state.value ?? const <Tracker>[];
final List<Tracker> next = current
.map(
(Tracker tracker) => tracker.id == id
? tracker.copyWith(readChapters: const <String>{})
: tracker,
)
.toList(growable: false);
await _persist(next);
}
}
final StateNotifierProvider<TrackerService, AsyncValue<List<Tracker>>>
trackerServiceProvider =
StateNotifierProvider<TrackerService, AsyncValue<List<Tracker>>>(
(Ref ref) => TrackerService(ref.read(trackerStorageProvider)),
);
/// Convenience selector for a single tracker by id.
final ProviderFamily<Tracker?, String> trackerByIdProvider =
Provider.family<Tracker?, String>((Ref ref, String id) {
final AsyncValue<List<Tracker>> state = ref.watch(trackerServiceProvider);
return state.value?.cast<Tracker?>().firstWhere(
(Tracker? t) => t?.id == id,
orElse: () => null,
);
});

View file

@ -0,0 +1,51 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/tracker.dart';
/// Persists the list of trackers to [SharedPreferences] as JSON.
///
/// A repository is justified here because storage is asynchronous, has
/// specialized queries (save/load all), and is consumed both by the service
/// layer and by tests with fake implementations.
class TrackerStorage {
TrackerStorage(this._prefs);
static const String _storageKey = 'bible_tracker.trackers.v1';
final SharedPreferences _prefs;
Future<List<Tracker>> loadAll() async {
final String? raw = _prefs.getString(_storageKey);
if (raw == null || raw.isEmpty) {
return <Tracker>[];
}
final List<dynamic> decoded = jsonDecode(raw) as List<dynamic>;
return decoded
.cast<Map<String, dynamic>>()
.map(Tracker.fromJson)
.toList(growable: false);
}
Future<void> saveAll(List<Tracker> trackers) async {
final List<Map<String, dynamic>> json = trackers
.map((Tracker tracker) => tracker.toJson())
.toList(growable: false);
await _prefs.setString(_storageKey, jsonEncode(json));
}
}
/// Must be overridden at startup with an initialized [SharedPreferences].
final Provider<SharedPreferences> sharedPreferencesProvider =
Provider<SharedPreferences>(
(Ref ref) => throw UnimplementedError(
'sharedPreferencesProvider must be overridden before use',
),
);
final Provider<TrackerStorage> trackerStorageProvider =
Provider<TrackerStorage>(
(Ref ref) => TrackerStorage(ref.read(sharedPreferencesProvider)),
);

View file

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../data/bible.dart';
import 'progress_bar.dart';
import 'theme.dart';
class BookTile extends StatelessWidget {
const BookTile({
super.key,
required this.book,
required this.chaptersRead,
required this.accent,
required this.onTap,
});
final Book book;
final int chaptersRead;
final Color accent;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final bool complete = chaptersRead == book.chapterCount;
final bool started = chaptersRead > 0 && !complete;
final double fraction = book.chapterCount == 0
? 0
: chaptersRead / book.chapterCount;
Color leadingColor;
IconData? leadingIcon;
if (complete) {
leadingColor = accent;
leadingIcon = Icons.check_rounded;
} else if (started) {
leadingColor = accent.withValues(alpha: 0.6);
leadingIcon = null;
} else {
leadingColor = Theme.of(context).colorScheme.surfaceContainerHighest;
leadingIcon = null;
}
return ListTile(
onTap: onTap,
leading: Container(
width: 32,
height: 32,
decoration: BoxDecoration(color: leadingColor, shape: BoxShape.circle),
alignment: Alignment.center,
child: leadingIcon != null
? Icon(leadingIcon, size: 18, color: Colors.white)
: Text(
'${book.chapterCount}',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: started
? Colors.white
: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
),
),
title: Text(
book.name,
style: Theme.of(
context,
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: AppTokens.spaceXS),
child: Row(
children: <Widget>[
Expanded(
child: ProgressBar(value: fraction, color: accent, height: 4),
),
const SizedBox(width: AppTokens.spaceM),
Text(
'$chaptersRead / ${book.chapterCount}',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'theme.dart';
/// A thin rounded progress bar used throughout the app.
class ProgressBar extends StatelessWidget {
const ProgressBar({
super.key,
required this.value,
required this.color,
this.height = 6,
});
final double value;
final Color color;
final double height;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(height / 2),
child: LinearProgressIndicator(
value: value.clamp(0.0, 1.0),
minHeight: height,
valueColor: AlwaysStoppedAnimation<Color>(color),
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
),
);
}
}
/// A pill showing a percentage, colored by the tracker/book accent.
class ProgressPill extends StatelessWidget {
const ProgressPill({super.key, required this.fraction, required this.color});
final double fraction;
final Color color;
@override
Widget build(BuildContext context) {
final int percent = (fraction * 100).round();
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppTokens.spaceM,
vertical: AppTokens.spaceXS,
),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(AppTokens.radiusL),
),
child: Text(
'$percent%',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
),
);
}
}

101
lib/widgets/theme.dart Normal file
View file

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
/// Design tokens and theme construction for Bible Tracker.
///
/// Refer to `DESIGN_GUIDE.md` for the rationale behind each token.
class AppTokens {
const AppTokens._();
// Seed color — warm, scripture-inspired indigo.
static const Color seed = Color(0xFF3F51B5);
// Default palette offered when creating a new tracker.
static const List<Color> trackerPalette = <Color>[
Color(0xFF3F51B5), // indigo
Color(0xFF1E88E5), // blue
Color(0xFF00897B), // teal
Color(0xFF43A047), // green
Color(0xFFFDD835), // amber
Color(0xFFFB8C00), // orange
Color(0xFFE53935), // red
Color(0xFFD81B60), // pink
Color(0xFF8E24AA), // purple
Color(0xFF6D4C41), // brown
];
// Spacing scale (8pt grid).
static const double spaceXS = 4;
static const double spaceS = 8;
static const double spaceM = 12;
static const double spaceL = 16;
static const double spaceXL = 24;
static const double spaceXXL = 32;
// Radius scale.
static const double radiusS = 8;
static const double radiusM = 12;
static const double radiusL = 16;
}
class AppTheme {
const AppTheme._();
static ThemeData light() => _build(Brightness.light);
static ThemeData dark() => _build(Brightness.dark);
static ThemeData _build(Brightness brightness) {
final ColorScheme scheme = ColorScheme.fromSeed(
seedColor: AppTokens.seed,
brightness: brightness,
);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: scheme.surface,
appBarTheme: AppBarTheme(
backgroundColor: scheme.surface,
foregroundColor: scheme.onSurface,
elevation: 0,
centerTitle: false,
titleTextStyle: TextStyle(
color: scheme.onSurface,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
cardTheme: CardThemeData(
color: scheme.surfaceContainerHigh,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppTokens.radiusM),
),
),
listTileTheme: const ListTileThemeData(
contentPadding: EdgeInsets.symmetric(
horizontal: AppTokens.spaceL,
vertical: AppTokens.spaceS,
),
),
dividerTheme: DividerThemeData(
color: scheme.outlineVariant,
thickness: 0.5,
space: 0,
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: AppTokens.spaceXL,
vertical: AppTokens.spaceM,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppTokens.radiusS),
),
),
),
progressIndicatorTheme: ProgressIndicatorThemeData(
linearTrackColor: scheme.surfaceContainerHighest,
),
);
}
}

View file

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import '../models/tracker.dart';
import '../services/progress.dart';
import 'progress_bar.dart';
import 'theme.dart';
class TrackerTile extends StatelessWidget {
const TrackerTile({
super.key,
required this.tracker,
required this.onTap,
this.onLongPress,
});
final Tracker tracker;
final VoidCallback onTap;
final VoidCallback? onLongPress;
@override
Widget build(BuildContext context) {
final Color color = Color(tracker.colorValue);
final double fraction = TrackerProgress(tracker).overall;
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
onLongPress: onLongPress,
child: Padding(
padding: const EdgeInsets.all(AppTokens.spaceL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: AppTokens.spaceM),
Expanded(
child: Text(
tracker.name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
ProgressPill(fraction: fraction, color: color),
],
),
const SizedBox(height: AppTokens.spaceM),
ProgressBar(value: fraction, color: color),
],
),
),
),
);
}
}

7
macos/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View file

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

View file

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

View file

@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

42
macos/Podfile Normal file
View file

@ -0,0 +1,42 @@
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

View 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 /* bible_tracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "bible_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 /* bible_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 /* bible_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.matt.bibleTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bible_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bible_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.matt.bibleTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bible_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bible_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.matt.bibleTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bible_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bible_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.15;
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.15;
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.15;
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 */;
}

View file

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

View file

@ -0,0 +1,99 @@
<?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 = "bible_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 = "bible_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"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "bible_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 = "bible_tracker.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

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

View file

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

View file

@ -0,0 +1,13 @@
import Cocoa
import FlutterMacOS
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}

View file

@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

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