feat: checkpoint API migration and dzikir UX updates
@@ -18,6 +18,12 @@ migration:
|
||||
- platform: android
|
||||
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||
- platform: ios
|
||||
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||
- platform: macos
|
||||
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.jamshalat.jamshalat_diary
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -1,5 +1,5 @@
|
||||
sdk.dir=/Users/dwindown/Library/Android/sdk
|
||||
flutter.sdk=/Users/dwindown/FlutterDev/flutter
|
||||
flutter.sdk=/opt/homebrew/share/flutter
|
||||
flutter.buildMode=release
|
||||
flutter.versionName=1.0.0
|
||||
flutter.versionCode=1
|
||||
BIN
assets/images/icon.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
assets/images/logo_normal.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/logo_white.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
81
dzikir-display-mode-ux-brief.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Dzikir Display Mode UX Brief
|
||||
|
||||
## 1) Objective
|
||||
Provide two complementary experiences for Dzikir:
|
||||
|
||||
- **Daftar (Baris)** for fast scanning and jumping between items.
|
||||
- **Fokus (Slide)** for one-item focus with consistent thumb reach and counting flow.
|
||||
|
||||
This mode applies to all Dzikir tabs: **Pagi**, **Petang**, and **Sesudah Shalat**.
|
||||
|
||||
## 2) Settings Specification
|
||||
|
||||
Section name in Settings: **Tampilan Dzikir**
|
||||
|
||||
| Label | Type | Options | Default | Visibility |
|
||||
|---|---|---|---|---|
|
||||
| `Mode Tampilan Dzikir` | Segmented | `Daftar (Baris)` / `Fokus (Slide)` | `Daftar (Baris)` | Always |
|
||||
| `Posisi Tombol Hitung` | Segmented | `Pill Bawah (Disarankan)` / `Bulat Kanan Bawah` | `Pill Bawah (Disarankan)` | Only in `Fokus (Slide)` |
|
||||
| `Lanjut Otomatis Saat Target Tercapai` | Switch | `On/Off` | `On` | Only in `Fokus (Slide)` |
|
||||
| `Getaran Saat Hitung` | Switch | `On/Off` | `On` | Always |
|
||||
|
||||
## 3) Interaction Rules
|
||||
|
||||
### A. Mode: Daftar (Baris)
|
||||
- Keep current row-based list and per-row counter pattern.
|
||||
- Users can scan, jump, and increment any row directly.
|
||||
- Counter behavior remains per item, per day.
|
||||
|
||||
### B. Mode: Fokus (Slide)
|
||||
- Display exactly **one dzikir item per slide**.
|
||||
- Horizontal swipe moves between dzikir items.
|
||||
- Counter button is fixed in one location (based on selected button position).
|
||||
- Top area displays progress: `Item X dari Y`.
|
||||
- Tapping counter increments by `+1` until target.
|
||||
- When target reached:
|
||||
- Mark item as complete.
|
||||
- If `Lanjut Otomatis... = On`, move to next slide automatically (except last item).
|
||||
|
||||
## 4) Button Placement Recommendation
|
||||
|
||||
Primary recommendation:
|
||||
|
||||
- **Pill Bawah (Disarankan)** as default in Focus mode.
|
||||
|
||||
Reason:
|
||||
- Better one-handed ergonomics.
|
||||
- Consistent location improves counting rhythm.
|
||||
- Larger tap target lowers miss taps while reciting.
|
||||
|
||||
Optional style:
|
||||
- **Bulat Kanan Bawah** for users preferring minimal visual footprint.
|
||||
|
||||
## 5) Data & State Behavior
|
||||
|
||||
- Counter data is shared across modes (switching mode must not reset progress).
|
||||
- Existing daily tracking logic remains unchanged.
|
||||
- Switching mode keeps current tab (`Pagi/Petang/Sesudah Shalat`) intact.
|
||||
- Completed state must be reflected identically in both modes.
|
||||
|
||||
## 6) Completion & Feedback UX
|
||||
|
||||
- Counter states: `normal` and `completed`.
|
||||
- Completed label example: `Selesai`.
|
||||
- Last item completion feedback:
|
||||
- Show subtle confirmation message: `Semua dzikir pada tab ini selesai`.
|
||||
- Empty or missing data:
|
||||
- Show friendly empty state, never blank screen.
|
||||
|
||||
## 7) Default Product Decision
|
||||
|
||||
- App default: **Daftar (Baris)** for broad familiarity.
|
||||
- Advanced/focus users can enable **Fokus (Slide)**.
|
||||
- In Focus mode, default button placement: **Pill Bawah (Disarankan)**.
|
||||
|
||||
## 8) Success Criteria
|
||||
|
||||
- Users can switch between modes without losing count progress.
|
||||
- Focus mode reduces hand travel for repeated taps.
|
||||
- Both modes remain consistent across all Dzikir tabs.
|
||||
- No behavioral mismatch between count target, completion state, and progress indicator.
|
||||
|
||||
34
ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
24
ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
2
ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -1,6 +1,6 @@
|
||||
// This is a generated file; do not edit or check into version control.
|
||||
FLUTTER_ROOT=/Users/dwindown/FlutterDev/flutter
|
||||
FLUTTER_APPLICATION_PATH=/Users/dwindown/CascadeProjects/jamshalat-diary
|
||||
FLUTTER_ROOT=/opt/homebrew/share/flutter
|
||||
FLUTTER_APPLICATION_PATH=/Users/dwindown/Applications/jamshalat-diary
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_TARGET=lib/main.dart
|
||||
FLUTTER_BUILD_DIR=build
|
||||
|
||||
2
ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# This is a generated file; do not edit or check into version control.
|
||||
export "FLUTTER_ROOT=/Users/dwindown/FlutterDev/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/Users/dwindown/CascadeProjects/jamshalat-diary"
|
||||
export "FLUTTER_ROOT=/opt/homebrew/share/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/Users/dwindown/Applications/jamshalat-diary"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_TARGET=lib/main.dart"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
|
||||
620
ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,620 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
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 */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
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 */,
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 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)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary;
|
||||
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.jamshalat.jamshalatDiary.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.jamshalat.jamshalatDiary.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.jamshalat.jamshalatDiary.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)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
101
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal 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>
|
||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
16
ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
}
|
||||
}
|
||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
70
ios/Runner/Info.plist
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Jamshalat Diary</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>jamshalat_diary</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
6
ios/Runner/SceneDelegate.swift
Normal file
@@ -0,0 +1,6 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: FlutterSceneDelegate {
|
||||
|
||||
}
|
||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,11 +11,14 @@ import '../features/checklist/presentation/checklist_screen.dart';
|
||||
import '../features/laporan/presentation/laporan_screen.dart';
|
||||
import '../features/tools/presentation/tools_screen.dart';
|
||||
import '../features/dzikir/presentation/dzikir_screen.dart';
|
||||
import '../features/doa/presentation/doa_screen.dart';
|
||||
import '../features/hadits/presentation/hadits_screen.dart';
|
||||
import '../features/qibla/presentation/qibla_screen.dart';
|
||||
import '../features/quran/presentation/quran_screen.dart';
|
||||
import '../features/quran/presentation/quran_reading_screen.dart';
|
||||
import '../features/quran/presentation/quran_murattal_screen.dart';
|
||||
import '../features/quran/presentation/quran_bookmarks_screen.dart';
|
||||
import '../features/quran/presentation/quran_enrichment_screen.dart';
|
||||
import '../features/settings/presentation/settings_screen.dart';
|
||||
|
||||
/// Navigation key for the shell navigator (bottom-nav screens).
|
||||
@@ -79,6 +82,11 @@ final GoRouter appRouter = GoRouter(
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const QuranScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'enrichment',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const QuranEnrichmentScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'bookmarks',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
@@ -116,6 +124,16 @@ final GoRouter appRouter = GoRouter(
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const QiblaScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'doa',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const DoaScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'hadits',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const HaditsScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Simple Mode Tab: Zikir
|
||||
@@ -128,6 +146,10 @@ final GoRouter appRouter = GoRouter(
|
||||
path: '/quran',
|
||||
builder: (context, state) => const QuranScreen(isSimpleModeTab: true),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'enrichment',
|
||||
builder: (context, state) => const QuranEnrichmentScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'bookmarks',
|
||||
builder: (context, state) => const QuranBookmarksScreen(),
|
||||
@@ -159,6 +181,14 @@ final GoRouter appRouter = GoRouter(
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/doa',
|
||||
builder: (context, state) => const DoaScreen(isSimpleModeTab: true),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/hadits',
|
||||
builder: (context, state) => const HaditsScreen(isSimpleModeTab: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
// ── Settings (pushed, no bottom nav) ──
|
||||
|
||||
@@ -64,7 +64,7 @@ class AppTextStyles {
|
||||
static const TextStyle arabicLarge = TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 2.2,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,18 @@ class AppSettings extends HiveObject {
|
||||
@HiveField(19)
|
||||
bool simpleMode; // false = Mode Lengkap, true = Mode Simpel
|
||||
|
||||
@HiveField(20)
|
||||
String dzikirDisplayMode; // 'list' | 'focus'
|
||||
|
||||
@HiveField(21)
|
||||
String dzikirCounterButtonPosition; // 'bottomPill' | 'fabCircle'
|
||||
|
||||
@HiveField(22)
|
||||
bool dzikirAutoAdvance;
|
||||
|
||||
@HiveField(23)
|
||||
bool dzikirHapticOnCount;
|
||||
|
||||
AppSettings({
|
||||
this.userName = 'User',
|
||||
this.userEmail = '',
|
||||
@@ -86,6 +98,10 @@ class AppSettings extends HiveObject {
|
||||
this.showLatin = true,
|
||||
this.showTerjemahan = true,
|
||||
this.simpleMode = false,
|
||||
this.dzikirDisplayMode = 'list',
|
||||
this.dzikirCounterButtonPosition = 'bottomPill',
|
||||
this.dzikirAutoAdvance = true,
|
||||
this.dzikirHapticOnCount = true,
|
||||
}) : adhanEnabled = adhanEnabled ??
|
||||
{
|
||||
'fajr': true,
|
||||
|
||||
@@ -37,13 +37,17 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
|
||||
showLatin: fields.containsKey(17) ? fields[17] as bool? ?? true : true,
|
||||
showTerjemahan: fields.containsKey(18) ? fields[18] as bool? ?? true : true,
|
||||
simpleMode: fields.containsKey(19) ? fields[19] as bool? ?? false : false,
|
||||
dzikirDisplayMode: fields.containsKey(20) ? fields[20] as String? ?? 'list' : 'list',
|
||||
dzikirCounterButtonPosition: fields.containsKey(21) ? fields[21] as String? ?? 'bottomPill' : 'bottomPill',
|
||||
dzikirAutoAdvance: fields.containsKey(22) ? fields[22] as bool? ?? true : true,
|
||||
dzikirHapticOnCount: fields.containsKey(23) ? fields[23] as bool? ?? true : true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AppSettings obj) {
|
||||
writer
|
||||
..writeByte(20)
|
||||
..writeByte(24)
|
||||
..writeByte(0)
|
||||
..write(obj.userName)
|
||||
..writeByte(1)
|
||||
@@ -83,7 +87,15 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
|
||||
..writeByte(18)
|
||||
..write(obj.showTerjemahan)
|
||||
..writeByte(19)
|
||||
..write(obj.simpleMode);
|
||||
..write(obj.simpleMode)
|
||||
..writeByte(20)
|
||||
..write(obj.dzikirDisplayMode)
|
||||
..writeByte(21)
|
||||
..write(obj.dzikirCounterButtonPosition)
|
||||
..writeByte(22)
|
||||
..write(obj.dzikirAutoAdvance)
|
||||
..writeByte(23)
|
||||
..write(obj.dzikirHapticOnCount);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
561
lib/data/services/muslim_api_service.dart
Normal file
@@ -0,0 +1,561 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class MuslimApiException implements Exception {
|
||||
final String message;
|
||||
const MuslimApiException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'MuslimApiException: $message';
|
||||
}
|
||||
|
||||
/// Service for muslim.backoffice.biz.id API.
|
||||
///
|
||||
/// Exposes Quran, dzikir, doa, hadits, and enrichment data while preserving
|
||||
/// the data contract currently expected by Quran and dashboard UI widgets.
|
||||
class MuslimApiService {
|
||||
static const String _baseUrl = 'https://muslim.backoffice.biz.id';
|
||||
static final MuslimApiService instance = MuslimApiService._();
|
||||
|
||||
MuslimApiService._();
|
||||
|
||||
static const Map<String, String> qariNames = {
|
||||
'01': 'Abdullah Al-Juhany',
|
||||
'02': 'Abdul Muhsin Al-Qasim',
|
||||
'03': 'Abdurrahman As-Sudais',
|
||||
'04': 'Ibrahim Al-Dossari',
|
||||
'05': 'Misyari Rasyid Al-Afasi',
|
||||
'06': 'Yasser Al-Dosari',
|
||||
};
|
||||
|
||||
List<Map<String, dynamic>>? _surahListCache;
|
||||
final Map<int, Map<String, dynamic>> _surahCache = {};
|
||||
|
||||
List<Map<String, dynamic>>? _allAyahCache;
|
||||
List<Map<String, dynamic>>? _tafsirCache;
|
||||
List<Map<String, dynamic>>? _asbabCache;
|
||||
List<Map<String, dynamic>>? _juzCache;
|
||||
List<Map<String, dynamic>>? _themeCache;
|
||||
List<Map<String, dynamic>>? _asmaCache;
|
||||
List<Map<String, dynamic>>? _doaCache;
|
||||
List<Map<String, dynamic>>? _haditsCache;
|
||||
|
||||
final Map<String, List<Map<String, dynamic>>> _dzikirByTypeCache = {};
|
||||
final Map<String, List<Map<String, dynamic>>> _wordByWordCache = {};
|
||||
final Map<int, List<Map<String, dynamic>>> _pageAyahCache = {};
|
||||
|
||||
Future<dynamic> _getData(String path) async {
|
||||
try {
|
||||
final response = await http.get(Uri.parse('$_baseUrl$path'));
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
final decoded = json.decode(response.body);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
return decoded['data'];
|
||||
}
|
||||
return null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> _getDataOrThrow(String path) async {
|
||||
final response = await http.get(Uri.parse('$_baseUrl$path'));
|
||||
if (response.statusCode != 200) {
|
||||
throw MuslimApiException(
|
||||
'Request failed ($path): HTTP ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
|
||||
final decoded = json.decode(response.body);
|
||||
if (decoded is! Map<String, dynamic>) {
|
||||
throw const MuslimApiException('Invalid API payload shape');
|
||||
}
|
||||
|
||||
final status = _asInt(decoded['status']);
|
||||
if (status != 200) {
|
||||
throw MuslimApiException('API returned non-200 status: $status');
|
||||
}
|
||||
|
||||
if (!decoded.containsKey('data')) {
|
||||
throw const MuslimApiException('API payload missing data key');
|
||||
}
|
||||
|
||||
return decoded['data'];
|
||||
}
|
||||
|
||||
int _asInt(dynamic value, {int fallback = 0}) {
|
||||
if (value is int) return value;
|
||||
if (value is num) return value.toInt();
|
||||
if (value is String) return int.tryParse(value) ?? fallback;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
String _asString(dynamic value, {String fallback = ''}) {
|
||||
if (value == null) return fallback;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
int _asCount(dynamic value, {int fallback = 1}) {
|
||||
if (value == null) return fallback;
|
||||
if (value is int) return value;
|
||||
if (value is num) return value.toInt();
|
||||
final text = value.toString();
|
||||
final match = RegExp(r'\d+').firstMatch(text);
|
||||
if (match == null) return fallback;
|
||||
return int.tryParse(match.group(0)!) ?? fallback;
|
||||
}
|
||||
|
||||
String _stableDzikirId(String type, Map<String, dynamic> item) {
|
||||
final apiId = _asString(item['id']);
|
||||
if (apiId.isNotEmpty) {
|
||||
return '${type}_$apiId';
|
||||
}
|
||||
|
||||
final seed = [
|
||||
type,
|
||||
_asString(item['type']),
|
||||
_asString(item['arab']),
|
||||
_asString(item['indo']),
|
||||
_asString(item['ulang']),
|
||||
].join('|');
|
||||
|
||||
var hash = 0;
|
||||
for (final unit in seed.codeUnits) {
|
||||
hash = ((hash * 31) + unit) & 0x7fffffff;
|
||||
}
|
||||
return '${type}_$hash';
|
||||
}
|
||||
|
||||
String _dzikirApiType(String type) {
|
||||
switch (type) {
|
||||
case 'petang':
|
||||
return 'sore';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> _normalizeAudioMap(dynamic audioValue) {
|
||||
final audioUrl = _asString(audioValue);
|
||||
if (audioUrl.isEmpty) return {};
|
||||
return {
|
||||
'01': audioUrl,
|
||||
'02': audioUrl,
|
||||
'03': audioUrl,
|
||||
'04': audioUrl,
|
||||
'05': audioUrl,
|
||||
'06': audioUrl,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _mapSurahSummary(Map<String, dynamic> item) {
|
||||
final number = _asInt(item['number']);
|
||||
return {
|
||||
'nomor': number,
|
||||
'nama': _asString(item['name_short']),
|
||||
'namaLatin': _asString(item['name_id']),
|
||||
'jumlahAyat': _asInt(item['number_of_verses']),
|
||||
'tempatTurun': _asString(item['revelation_id']),
|
||||
'arti': _asString(item['translation_id']),
|
||||
'deskripsi': _asString(item['tafsir']),
|
||||
'audioFull': _normalizeAudioMap(item['audio_url']),
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _mapAyah(Map<String, dynamic> item) {
|
||||
final audio = _asString(item['audio']);
|
||||
return {
|
||||
'nomorAyat': _asInt(item['ayah']),
|
||||
'teksArab': _asString(item['arab']),
|
||||
'teksLatin': _asString(item['latin']),
|
||||
'teksIndonesia': _asString(item['text']),
|
||||
'audio': {
|
||||
'01': audio,
|
||||
'02': audio,
|
||||
'03': audio,
|
||||
'04': audio,
|
||||
'05': audio,
|
||||
'06': audio,
|
||||
},
|
||||
'juz': _asInt(item['juz']),
|
||||
'page': _asInt(item['page']),
|
||||
'hizb': _asInt(item['hizb']),
|
||||
'theme': _asString(item['theme']),
|
||||
'asbab': _asString(item['asbab']),
|
||||
'notes': _asString(item['notes']),
|
||||
'surah': _asInt(item['surah']),
|
||||
'ayahId': _asInt(item['id']),
|
||||
};
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getAllSurahs() async {
|
||||
if (_surahListCache != null) return _surahListCache!;
|
||||
final raw = await _getData('/v1/quran/surah');
|
||||
if (raw is! List) return [];
|
||||
_surahListCache = raw
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(_mapSurahSummary)
|
||||
.toList();
|
||||
return _surahListCache!;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getSurah(int number) async {
|
||||
if (_surahCache.containsKey(number)) {
|
||||
return _surahCache[number];
|
||||
}
|
||||
|
||||
final surahs = await getAllSurahs();
|
||||
Map<String, dynamic>? summary;
|
||||
for (final surah in surahs) {
|
||||
if (surah['nomor'] == number) {
|
||||
summary = surah;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final rawAyah = await _getData('/v1/quran/ayah/surah?id=$number');
|
||||
if (summary == null || rawAyah is! List) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final mappedAyah = rawAyah
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(_mapAyah)
|
||||
.toList();
|
||||
|
||||
final mapped = {
|
||||
...summary,
|
||||
'ayat': mappedAyah,
|
||||
};
|
||||
_surahCache[number] = mapped;
|
||||
return mapped;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getDailyAyat() async {
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final dayOfYear = now.difference(DateTime(now.year, 1, 1)).inDays;
|
||||
final surahId = (dayOfYear % 114) + 1;
|
||||
final surah = await getSurah(surahId);
|
||||
if (surah == null) return null;
|
||||
|
||||
final ayat = List<Map<String, dynamic>>.from(surah['ayat'] ?? []);
|
||||
if (ayat.isEmpty) return null;
|
||||
|
||||
final ayatIndex = dayOfYear % ayat.length;
|
||||
final picked = ayat[ayatIndex];
|
||||
return {
|
||||
'surahName': surah['namaLatin'] ?? '',
|
||||
'nomorSurah': surahId,
|
||||
'nomorAyat': picked['nomorAyat'] ?? 1,
|
||||
'teksArab': picked['teksArab'] ?? '',
|
||||
'teksIndonesia': picked['teksIndonesia'] ?? '',
|
||||
};
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getWordByWord(int surahId, int ayahId) async {
|
||||
final key = '$surahId:$ayahId';
|
||||
if (_wordByWordCache.containsKey(key)) return _wordByWordCache[key]!;
|
||||
|
||||
final raw = await _getData('/v1/quran/word/ayah?surahId=$surahId&ayahId=$ayahId');
|
||||
if (raw is! List) return [];
|
||||
|
||||
final mapped = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'word': _asString(item['word']),
|
||||
'arab': _asString(item['arab']),
|
||||
'indo': _asString(item['indo']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
_wordByWordCache[key] = mapped;
|
||||
return mapped;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getAllAyah() async {
|
||||
if (_allAyahCache != null) return _allAyahCache!;
|
||||
final raw = await _getData('/v1/quran/ayah');
|
||||
if (raw is! List) return [];
|
||||
|
||||
_allAyahCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'id': _asInt(item['id']),
|
||||
'surah': _asInt(item['surah']),
|
||||
'ayah': _asInt(item['ayah']),
|
||||
'arab': _asString(item['arab']),
|
||||
'latin': _asString(item['latin']),
|
||||
'text': _asString(item['text']),
|
||||
'juz': _asInt(item['juz']),
|
||||
'page': _asInt(item['page']),
|
||||
'hizb': _asInt(item['hizb']),
|
||||
'theme': _asString(item['theme']),
|
||||
'asbab': _asString(item['asbab']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return _allAyahCache!;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getTafsirBySurah(int surahId) async {
|
||||
if (_tafsirCache == null) {
|
||||
final raw = await _getData('/v1/quran/tafsir');
|
||||
if (raw is! List) return [];
|
||||
_tafsirCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'id': _asInt(item['id']),
|
||||
'ayah': _asInt(item['ayah']),
|
||||
'wajiz': _asString(item['wajiz']),
|
||||
'tahlili': _asString(item['tahlili']),
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
final allAyah = await getAllAyah();
|
||||
if (allAyah.isEmpty || _tafsirCache == null) return [];
|
||||
|
||||
final ayahById = <int, Map<String, dynamic>>{};
|
||||
final ayahBySurahAyah = <String, Map<String, dynamic>>{};
|
||||
for (final ayah in allAyah) {
|
||||
final id = _asInt(ayah['id']);
|
||||
final surah = _asInt(ayah['surah']);
|
||||
final ayahNumber = _asInt(ayah['ayah']);
|
||||
ayahById[id] = ayah;
|
||||
ayahBySurahAyah['$surah:$ayahNumber'] = ayah;
|
||||
}
|
||||
|
||||
final result = <Map<String, dynamic>>[];
|
||||
for (final tafsir in _tafsirCache!) {
|
||||
final tafsirId = _asInt(tafsir['id']);
|
||||
final tafsirAyah = _asInt(tafsir['ayah']);
|
||||
Map<String, dynamic>? ayahMeta = ayahById[tafsirId];
|
||||
ayahMeta ??= ayahBySurahAyah['$surahId:$tafsirAyah'];
|
||||
if (ayahMeta == null) continue;
|
||||
if (ayahMeta['surah'] != surahId) continue;
|
||||
result.add({
|
||||
'nomorAyat': _asInt(ayahMeta['ayah'], fallback: tafsirAyah),
|
||||
'wajiz': tafsir['wajiz'],
|
||||
'tahlili': tafsir['tahlili'],
|
||||
});
|
||||
}
|
||||
|
||||
result.sort((a, b) => (a['nomorAyat'] as int).compareTo(b['nomorAyat'] as int));
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getAsbabBySurah(int surahId) async {
|
||||
if (_asbabCache == null) {
|
||||
final raw = await _getData('/v1/quran/asbab');
|
||||
if (raw is! List) return [];
|
||||
_asbabCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'id': _asInt(item['id']),
|
||||
'ayah': _asInt(item['ayah']),
|
||||
'text': _asString(item['text']),
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
final allAyah = await getAllAyah();
|
||||
if (allAyah.isEmpty || _asbabCache == null) return [];
|
||||
|
||||
final ayahById = <int, Map<String, dynamic>>{};
|
||||
final ayahBySurahAyah = <String, Map<String, dynamic>>{};
|
||||
for (final ayah in allAyah) {
|
||||
final id = _asInt(ayah['id']);
|
||||
final surah = _asInt(ayah['surah']);
|
||||
final ayahNumber = _asInt(ayah['ayah']);
|
||||
ayahById[id] = ayah;
|
||||
ayahBySurahAyah['$surah:$ayahNumber'] = ayah;
|
||||
}
|
||||
|
||||
final result = <Map<String, dynamic>>[];
|
||||
for (final asbab in _asbabCache!) {
|
||||
final asbabId = _asInt(asbab['id']);
|
||||
final asbabAyah = _asInt(asbab['ayah']);
|
||||
Map<String, dynamic>? ayahMeta = ayahById[asbabId];
|
||||
ayahMeta ??= ayahBySurahAyah['$surahId:$asbabAyah'];
|
||||
if (ayahMeta == null) continue;
|
||||
if (ayahMeta['surah'] != surahId) continue;
|
||||
result.add({
|
||||
'nomorAyat': _asInt(ayahMeta['ayah'], fallback: asbabAyah),
|
||||
'text': asbab['text'],
|
||||
});
|
||||
}
|
||||
|
||||
result.sort((a, b) => (a['nomorAyat'] as int).compareTo(b['nomorAyat'] as int));
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getJuzList() async {
|
||||
if (_juzCache != null) return _juzCache!;
|
||||
final raw = await _getData('/v1/quran/juz');
|
||||
if (raw is! List) return [];
|
||||
|
||||
_juzCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'number': _asInt(item['number']),
|
||||
'name': _asString(item['name']),
|
||||
'surah_id_start': _asInt(item['surah_id_start']),
|
||||
'verse_start': _asInt(item['verse_start']),
|
||||
'surah_id_end': _asInt(item['surah_id_end']),
|
||||
'verse_end': _asInt(item['verse_end']),
|
||||
'name_start_id': _asString(item['name_start_id']),
|
||||
'name_end_id': _asString(item['name_end_id']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return _juzCache!;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getAyahByPage(int page) async {
|
||||
if (_pageAyahCache.containsKey(page)) return _pageAyahCache[page]!;
|
||||
final raw = await _getData('/v1/quran/ayah/page?id=$page');
|
||||
if (raw is! List) return [];
|
||||
|
||||
final mapped = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'surah': _asInt(item['surah']),
|
||||
'ayah': _asInt(item['ayah']),
|
||||
'arab': _asString(item['arab']),
|
||||
'text': _asString(item['text']),
|
||||
'theme': _asString(item['theme']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
_pageAyahCache[page] = mapped;
|
||||
return mapped;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getThemes() async {
|
||||
if (_themeCache != null) return _themeCache!;
|
||||
final raw = await _getData('/v1/quran/theme');
|
||||
if (raw is! List) return [];
|
||||
|
||||
_themeCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'id': _asInt(item['id']),
|
||||
'name': _asString(item['name']),
|
||||
};
|
||||
}).toList();
|
||||
return _themeCache!;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> searchAyah(String query) async {
|
||||
final q = query.trim().toLowerCase();
|
||||
if (q.isEmpty) return [];
|
||||
|
||||
final allAyah = await getAllAyah();
|
||||
final results = allAyah.where((item) {
|
||||
final text = _asString(item['text']).toLowerCase();
|
||||
final latin = _asString(item['latin']).toLowerCase();
|
||||
final arab = _asString(item['arab']);
|
||||
return text.contains(q) || latin.contains(q) || arab.contains(query.trim());
|
||||
}).take(50).toList();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getAsmaulHusna() async {
|
||||
if (_asmaCache != null) return _asmaCache!;
|
||||
final raw = await _getData('/v1/quran/asma');
|
||||
if (raw is! List) return [];
|
||||
|
||||
_asmaCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'id': _asInt(item['id']),
|
||||
'arab': _asString(item['arab']),
|
||||
'latin': _asString(item['latin']),
|
||||
'indo': _asString(item['indo']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return _asmaCache!;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getDoaList({bool strict = false}) async {
|
||||
if (_doaCache != null) return _doaCache!;
|
||||
final raw = strict
|
||||
? await _getDataOrThrow('/v1/doa')
|
||||
: await _getData('/v1/doa');
|
||||
if (raw is! List) {
|
||||
if (strict) {
|
||||
throw const MuslimApiException('Invalid doa payload');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
_doaCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'judul': _asString(item['judul']),
|
||||
'arab': _asString(item['arab']),
|
||||
'indo': _asString(item['indo']),
|
||||
'source': _asString(item['source']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return _doaCache!;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getHaditsList({bool strict = false}) async {
|
||||
if (_haditsCache != null) return _haditsCache!;
|
||||
final raw = strict
|
||||
? await _getDataOrThrow('/v1/hadits')
|
||||
: await _getData('/v1/hadits');
|
||||
if (raw is! List) {
|
||||
if (strict) {
|
||||
throw const MuslimApiException('Invalid hadits payload');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
_haditsCache = raw.whereType<Map<String, dynamic>>().map((item) {
|
||||
return {
|
||||
'no': _asInt(item['no']),
|
||||
'judul': _asString(item['judul']),
|
||||
'arab': _asString(item['arab']),
|
||||
'indo': _asString(item['indo']),
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return _haditsCache!;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getDzikirByType(
|
||||
String type, {
|
||||
bool strict = false,
|
||||
}) async {
|
||||
if (_dzikirByTypeCache.containsKey(type)) {
|
||||
return _dzikirByTypeCache[type]!;
|
||||
}
|
||||
final apiType = _dzikirApiType(type);
|
||||
final raw = strict
|
||||
? await _getDataOrThrow('/v1/dzikir?type=$apiType')
|
||||
: await _getData('/v1/dzikir?type=$apiType');
|
||||
if (raw is! List) {
|
||||
if (strict) {
|
||||
throw MuslimApiException('Invalid dzikir payload for type: $type');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
final mapped = <Map<String, dynamic>>[];
|
||||
for (var i = 0; i < raw.length; i++) {
|
||||
final item = raw[i];
|
||||
if (item is! Map<String, dynamic>) continue;
|
||||
mapped.add({
|
||||
'id': _stableDzikirId(type, item),
|
||||
'arab': _asString(item['arab']),
|
||||
'indo': _asString(item['indo']),
|
||||
'type': _asString(item['type']),
|
||||
'ulang': _asCount(item['ulang'], fallback: 1),
|
||||
});
|
||||
}
|
||||
|
||||
_dzikirByTypeCache[type] = mapped;
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import '../../../core/widgets/tool_card.dart';
|
||||
import '../../../data/local/hive_boxes.dart';
|
||||
import '../../../data/local/models/app_settings.dart';
|
||||
import '../../../data/local/models/daily_worship_log.dart';
|
||||
import '../../../data/services/equran_service.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
import '../data/prayer_times_provider.dart';
|
||||
|
||||
class DashboardScreen extends ConsumerStatefulWidget {
|
||||
@@ -810,13 +810,57 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.heart,
|
||||
title: 'Kumpulan\nDoa',
|
||||
color: const Color(0xFFE17055),
|
||||
isDark: isDark,
|
||||
onTap: () {
|
||||
final isSimple = Hive.box<AppSettings>(HiveBoxes.settings)
|
||||
.get('default')
|
||||
?.simpleMode ??
|
||||
false;
|
||||
if (isSimple) {
|
||||
context.push('/doa');
|
||||
} else {
|
||||
context.push('/tools/doa');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.library,
|
||||
title: "Hadits\nArba'in",
|
||||
color: const Color(0xFF6C5CE7),
|
||||
isDark: isDark,
|
||||
onTap: () {
|
||||
final isSimple = Hive.box<AppSettings>(HiveBoxes.settings)
|
||||
.get('default')
|
||||
?.simpleMode ??
|
||||
false;
|
||||
if (isSimple) {
|
||||
context.push('/hadits');
|
||||
} else {
|
||||
context.push('/tools/hadits');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAyatHariIni(BuildContext context, bool isDark) {
|
||||
return FutureBuilder<Map<String, dynamic>?>(
|
||||
future: EQuranService.instance.getDailyAyat(),
|
||||
future: MuslimApiService.instance.getDailyAyat(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Container(
|
||||
@@ -870,6 +914,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.8,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
|
||||
206
lib/features/doa/presentation/doa_screen.dart
Normal file
@@ -0,0 +1,206 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../../../app/theme/app_colors.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
|
||||
class DoaScreen extends StatefulWidget {
|
||||
final bool isSimpleModeTab;
|
||||
const DoaScreen({super.key, this.isSimpleModeTab = false});
|
||||
|
||||
@override
|
||||
State<DoaScreen> createState() => _DoaScreenState();
|
||||
}
|
||||
|
||||
class _DoaScreenState extends State<DoaScreen> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
List<Map<String, dynamic>> _allDoa = [];
|
||||
List<Map<String, dynamic>> _filteredDoa = [];
|
||||
bool _loading = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadDoa();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadDoa() async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final data = await MuslimApiService.instance.getDoaList(strict: true);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_allDoa = data;
|
||||
_filteredDoa = data;
|
||||
_loading = false;
|
||||
});
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_allDoa = [];
|
||||
_filteredDoa = [];
|
||||
_loading = false;
|
||||
_error = 'Gagal memuat doa dari server';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearchChanged(String value) {
|
||||
final q = value.trim().toLowerCase();
|
||||
if (q.isEmpty) {
|
||||
setState(() => _filteredDoa = _allDoa);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_filteredDoa = _allDoa.where((item) {
|
||||
final title = item['judul']?.toString().toLowerCase() ?? '';
|
||||
final indo = item['indo']?.toString().toLowerCase() ?? '';
|
||||
return title.contains(q) || indo.contains(q);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: !widget.isSimpleModeTab,
|
||||
title: const Text('Kumpulan Doa'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _loadDoa,
|
||||
icon: const Icon(LucideIcons.refreshCw),
|
||||
tooltip: 'Muat ulang',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
onChanged: _onSearchChanged,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Cari judul atau isi doa...',
|
||||
prefixIcon: const Icon(LucideIcons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? Center(
|
||||
child: Text(
|
||||
_error!,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: _filteredDoa.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'Doa tidak ditemukan',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
itemCount: _filteredDoa.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _filteredDoa[index];
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? AppColors.surfaceDark
|
||||
: AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.1)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item['judul']?.toString() ?? '-',
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
item['arab']?.toString() ?? '',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
item['indo']?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
if ((item['source']?.toString().isNotEmpty ??
|
||||
false)) ...[
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Sumber: ${item['source']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
import '../../../app/theme/app_colors.dart';
|
||||
import '../../../data/local/hive_boxes.dart';
|
||||
import '../../../data/local/models/dzikir_counter.dart';
|
||||
import '../../../data/local/models/app_settings.dart';
|
||||
import '../../../data/local/models/dzikir_counter.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
|
||||
class DzikirScreen extends ConsumerStatefulWidget {
|
||||
final bool isSimpleModeTab;
|
||||
@@ -21,15 +22,36 @@ class DzikirScreen extends ConsumerStatefulWidget {
|
||||
class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
final Map<String, PageController> _pageControllers = {
|
||||
'pagi': PageController(),
|
||||
'petang': PageController(),
|
||||
'solat': PageController(),
|
||||
};
|
||||
|
||||
final Map<String, int> _focusPageIndex = {
|
||||
'pagi': 0,
|
||||
'petang': 0,
|
||||
'solat': 0,
|
||||
};
|
||||
|
||||
List<Map<String, dynamic>> _pagiItems = [];
|
||||
List<Map<String, dynamic>> _petangItems = [];
|
||||
List<Map<String, dynamic>> _sesudahSholatItems = [];
|
||||
bool _loading = true;
|
||||
String? _error;
|
||||
|
||||
late Box<DzikirCounter> _counterBox;
|
||||
late String _todayKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
_tabController.addListener(() {
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
});
|
||||
_counterBox = Hive.box<DzikirCounter>(HiveBoxes.dzikirCounters);
|
||||
_todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now());
|
||||
_loadData();
|
||||
@@ -38,17 +60,68 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
for (final controller in _pageControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadData() async {
|
||||
final pagiJson =
|
||||
await rootBundle.loadString('assets/dzikir/dzikir_pagi.json');
|
||||
final petangJson =
|
||||
await rootBundle.loadString('assets/dzikir/dzikir_petang.json');
|
||||
setState(() {
|
||||
_pagiItems = List<Map<String, dynamic>>.from(json.decode(pagiJson));
|
||||
_petangItems = List<Map<String, dynamic>>.from(json.decode(petangJson));
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final pagi = await MuslimApiService.instance.getDzikirByType(
|
||||
'pagi',
|
||||
strict: true,
|
||||
);
|
||||
final petang = await MuslimApiService.instance.getDzikirByType(
|
||||
'petang',
|
||||
strict: true,
|
||||
);
|
||||
final solat = await MuslimApiService.instance.getDzikirByType(
|
||||
'solat',
|
||||
strict: true,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_pagiItems = pagi;
|
||||
_petangItems = petang;
|
||||
_sesudahSholatItems = solat;
|
||||
_loading = false;
|
||||
});
|
||||
_ensureValidFocusPages();
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_loading = false;
|
||||
_error = 'Gagal memuat dzikir dari server';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _ensureValidFocusPages() {
|
||||
_clampFocusPageForPrefix('pagi', _pagiItems.length);
|
||||
_clampFocusPageForPrefix('petang', _petangItems.length);
|
||||
_clampFocusPageForPrefix('solat', _sesudahSholatItems.length);
|
||||
}
|
||||
|
||||
void _clampFocusPageForPrefix(String prefix, int itemLength) {
|
||||
final maxIndex = itemLength > 0 ? itemLength - 1 : 0;
|
||||
final current = _focusPageIndex[prefix] ?? 0;
|
||||
final next = current > maxIndex ? maxIndex : current;
|
||||
_focusPageIndex[prefix] = next;
|
||||
|
||||
final controller = _pageControllers[prefix];
|
||||
if (controller == null || !controller.hasClients) return;
|
||||
if (controller.page?.round() == next) return;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || !controller.hasClients) return;
|
||||
controller.jumpToPage(next);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,9 +136,15 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
);
|
||||
}
|
||||
|
||||
void _increment(String dzikirId, int target) {
|
||||
bool _increment(
|
||||
String dzikirId,
|
||||
int target, {
|
||||
required bool hapticEnabled,
|
||||
}) {
|
||||
final key = '${dzikirId}_$_todayKey';
|
||||
var counter = _counterBox.get(key);
|
||||
final wasComplete = counter != null && counter.count >= counter.target;
|
||||
|
||||
if (counter == null) {
|
||||
counter = DzikirCounter(
|
||||
dzikirId: dzikirId,
|
||||
@@ -74,40 +153,42 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
target: target,
|
||||
);
|
||||
_counterBox.put(key, counter);
|
||||
} else {
|
||||
if (counter.count < counter.target) {
|
||||
counter.count++;
|
||||
counter.save();
|
||||
}
|
||||
} else if (counter.count < counter.target) {
|
||||
counter.count++;
|
||||
counter.save();
|
||||
}
|
||||
|
||||
final isCompleteNow = counter.count >= counter.target;
|
||||
if (hapticEnabled) {
|
||||
HapticFeedback.lightImpact();
|
||||
}
|
||||
setState(() {});
|
||||
// Haptic feedback
|
||||
HapticFeedback.lightImpact();
|
||||
return !wasComplete && isCompleteNow;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final box = Hive.box<AppSettings>(HiveBoxes.settings);
|
||||
final isSimpleMode = box.get('default')?.simpleMode ?? false;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: !widget.isSimpleModeTab,
|
||||
title: const Text('Dzikir Pagi & Petang'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(LucideIcons.info),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// Tabs
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: TabBar(
|
||||
return ValueListenableBuilder<Box<AppSettings>>(
|
||||
valueListenable:
|
||||
Hive.box<AppSettings>(HiveBoxes.settings).listenable(keys: ['default']),
|
||||
builder: (_, settingsBox, __) {
|
||||
final settings = settingsBox.get('default') ?? AppSettings();
|
||||
final isFocusMode = settings.dzikirDisplayMode == 'focus';
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: !widget.isSimpleModeTab,
|
||||
title: const Text('Dzikir Harian'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _loadData,
|
||||
icon: const Icon(LucideIcons.refreshCw),
|
||||
tooltip: 'Muat ulang',
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: AppColors.primary,
|
||||
unselectedLabelColor: isDark
|
||||
@@ -116,47 +197,151 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
indicatorColor: AppColors.primary,
|
||||
indicatorWeight: 3,
|
||||
labelStyle:
|
||||
const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
||||
const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
|
||||
tabs: const [
|
||||
Tab(text: 'Pagi'),
|
||||
Tab(text: 'Petang'),
|
||||
Tab(text: 'Sesudah Sholat'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildDzikirList(context, isDark, _pagiItems, 'pagi',
|
||||
'Dzikir Pagi', 'Dibaca setelah shalat Shubuh hingga terbit matahari'),
|
||||
_buildDzikirList(context, isDark, _petangItems, 'petang',
|
||||
'Dzikir Petang', 'Dibaca setelah shalat Ashar hingga terbenam matahari'),
|
||||
],
|
||||
body: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? _buildErrorState(isDark)
|
||||
: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
isFocusMode
|
||||
? _buildFocusModeTab(
|
||||
context,
|
||||
isDark,
|
||||
settings,
|
||||
items: _pagiItems,
|
||||
prefix: 'pagi',
|
||||
title: 'Dzikir Pagi',
|
||||
subtitle:
|
||||
'Dibaca setelah shalat Subuh hingga terbit matahari.',
|
||||
)
|
||||
: _buildDzikirList(
|
||||
context,
|
||||
isDark,
|
||||
settings,
|
||||
_pagiItems,
|
||||
'pagi',
|
||||
'Dzikir Pagi',
|
||||
'Dibaca setelah shalat Subuh hingga terbit matahari.',
|
||||
),
|
||||
isFocusMode
|
||||
? _buildFocusModeTab(
|
||||
context,
|
||||
isDark,
|
||||
settings,
|
||||
items: _petangItems,
|
||||
prefix: 'petang',
|
||||
title: 'Dzikir Petang',
|
||||
subtitle:
|
||||
'Dibaca setelah Ashar hingga terbenam matahari.',
|
||||
)
|
||||
: _buildDzikirList(
|
||||
context,
|
||||
isDark,
|
||||
settings,
|
||||
_petangItems,
|
||||
'petang',
|
||||
'Dzikir Petang',
|
||||
'Dibaca setelah Ashar hingga terbenam matahari.',
|
||||
),
|
||||
isFocusMode
|
||||
? _buildFocusModeTab(
|
||||
context,
|
||||
isDark,
|
||||
settings,
|
||||
items: _sesudahSholatItems,
|
||||
prefix: 'solat',
|
||||
title: 'Dzikir Sesudah Sholat',
|
||||
subtitle:
|
||||
'Dibaca setelah shalat fardhu sesuai kebutuhan.',
|
||||
)
|
||||
: _buildDzikirList(
|
||||
context,
|
||||
isDark,
|
||||
settings,
|
||||
_sesudahSholatItems,
|
||||
'solat',
|
||||
'Dzikir Sesudah Sholat',
|
||||
'Dibaca setelah shalat fardhu sesuai kebutuhan.',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorState(bool isDark) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.wifiOff,
|
||||
size: 42,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_error!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDzikirList(BuildContext context, bool isDark,
|
||||
List<Map<String, dynamic>> items, String prefix, String title, String subtitle) {
|
||||
Widget _buildDzikirList(
|
||||
BuildContext context,
|
||||
bool isDark,
|
||||
AppSettings settings,
|
||||
List<Map<String, dynamic>> items,
|
||||
String prefix,
|
||||
String title,
|
||||
String subtitle,
|
||||
) {
|
||||
if (items.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return _buildEmptyState(
|
||||
isDark,
|
||||
title: 'Belum ada data dzikir',
|
||||
subtitle: 'Data untuk tab ini belum tersedia.',
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: items.length + 1, // +1 for header
|
||||
itemCount: items.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(title,
|
||||
style: const TextStyle(
|
||||
fontSize: 22, fontWeight: FontWeight.w800)),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
@@ -174,8 +359,8 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
}
|
||||
|
||||
final item = items[index - 1];
|
||||
final dzikirId = '${prefix}_${item['id']}';
|
||||
final target = (item['count'] as num?)?.toInt() ?? 1;
|
||||
final dzikirId = _resolveDzikirId(item, prefix, index - 1);
|
||||
final target = (item['ulang'] as num?)?.toInt() ?? 1;
|
||||
final counter = _getCounter(dzikirId, target);
|
||||
final isComplete = counter.count >= counter.target;
|
||||
|
||||
@@ -197,13 +382,14 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header row: count badge + number
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 4),
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
@@ -230,44 +416,37 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Arabic text
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
item['arabic'] ?? '',
|
||||
item['arab']?.toString() ?? '',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 2.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Transliteration
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
item['transliteration'] ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Translation
|
||||
Text(
|
||||
'"${item['translation'] ?? ''}"',
|
||||
'"${item['indo']?.toString() ?? ''}"',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Counter button
|
||||
GestureDetector(
|
||||
onTap: () => _increment(dzikirId, target),
|
||||
onTap: () => _increment(
|
||||
dzikirId,
|
||||
target,
|
||||
hapticEnabled: settings.dzikirHapticOnCount,
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
@@ -281,7 +460,9 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isComplete ? LucideIcons.check : LucideIcons.fingerprint,
|
||||
isComplete
|
||||
? LucideIcons.check
|
||||
: LucideIcons.fingerprint,
|
||||
size: 18,
|
||||
color: isComplete
|
||||
? AppColors.primary
|
||||
@@ -289,7 +470,7 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${counter.count} / $target',
|
||||
isComplete ? 'Selesai' : '${counter.count} / $target',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -309,4 +490,433 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFocusModeTab(
|
||||
BuildContext context,
|
||||
bool isDark,
|
||||
AppSettings settings, {
|
||||
required List<Map<String, dynamic>> items,
|
||||
required String prefix,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
}) {
|
||||
if (items.isEmpty) {
|
||||
return _buildEmptyState(
|
||||
isDark,
|
||||
title: 'Belum ada data dzikir',
|
||||
subtitle: 'Data untuk tab ini belum tersedia.',
|
||||
);
|
||||
}
|
||||
|
||||
final controller = _pageControllers[prefix]!;
|
||||
final rawCurrent = _focusPageIndex[prefix] ?? 0;
|
||||
final currentIndex = rawCurrent.clamp(0, items.length - 1);
|
||||
final currentItem = items[currentIndex];
|
||||
final currentId = _resolveDzikirId(currentItem, prefix, currentIndex);
|
||||
final currentTarget = (currentItem['ulang'] as num?)?.toInt() ?? 1;
|
||||
final currentCounter = _getCounter(currentId, currentTarget);
|
||||
final isComplete = currentCounter.count >= currentCounter.target;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w800),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Text(
|
||||
'Item ${currentIndex + 1} dari ${items.length}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
PageView.builder(
|
||||
controller: controller,
|
||||
itemCount: items.length,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
_focusPageIndex[prefix] = index;
|
||||
});
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
final dzikirId = _resolveDzikirId(item, prefix, index);
|
||||
final target = (item['ulang'] as num?)?.toInt() ?? 1;
|
||||
final counter = _getCounter(dzikirId, target);
|
||||
final complete = counter.count >= counter.target;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 92),
|
||||
child: _buildFocusCard(
|
||||
isDark,
|
||||
item: item,
|
||||
index: index,
|
||||
target: target,
|
||||
counter: counter,
|
||||
isComplete: complete,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (settings.dzikirCounterButtonPosition == 'fabCircle')
|
||||
Positioned(
|
||||
right: 8,
|
||||
bottom: 12,
|
||||
child: _buildFocusCounterFab(
|
||||
isDark,
|
||||
isComplete: isComplete,
|
||||
label: isComplete
|
||||
? 'Selesai'
|
||||
: '${currentCounter.count}/$currentTarget',
|
||||
onTap: () => _onFocusCounterTap(
|
||||
context,
|
||||
settings,
|
||||
prefix,
|
||||
items,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 12,
|
||||
child: _buildFocusCounterPill(
|
||||
isComplete: isComplete,
|
||||
label: isComplete
|
||||
? 'Selesai'
|
||||
: '${currentCounter.count} / $currentTarget',
|
||||
onTap: () => _onFocusCounterTap(
|
||||
context,
|
||||
settings,
|
||||
prefix,
|
||||
items,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFocusCard(
|
||||
bool isDark, {
|
||||
required Map<String, dynamic> item,
|
||||
required int index,
|
||||
required int target,
|
||||
required DzikirCounter counter,
|
||||
required bool isComplete,
|
||||
}) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isComplete
|
||||
? AppColors.primary.withValues(alpha: 0.3)
|
||||
: (isDark
|
||||
? AppColors.primary.withValues(alpha: 0.08)
|
||||
: AppColors.cream),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Text(
|
||||
'$target KALI',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${(index + 1).toString().padLeft(2, '0')}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
item['arab']?.toString() ?? '',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 2.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'"${item['indo']?.toString() ?? ''}"',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (isComplete)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Text(
|
||||
'Selesai (${counter.count}/$target)',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFocusCounterPill({
|
||||
required bool isComplete,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isComplete
|
||||
? AppColors.primary.withValues(alpha: 0.15)
|
||||
: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isComplete ? LucideIcons.check : LucideIcons.fingerprint,
|
||||
size: 18,
|
||||
color: isComplete ? AppColors.primary : AppColors.onPrimary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isComplete ? AppColors.primary : AppColors.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFocusCounterFab(
|
||||
bool isDark, {
|
||||
required bool isComplete,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 72,
|
||||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isComplete
|
||||
? AppColors.primary.withValues(alpha: 0.15)
|
||||
: AppColors.primary,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (isDark ? Colors.black : Colors.black26)
|
||||
.withValues(alpha: 0.14),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isComplete ? LucideIcons.check : LucideIcons.fingerprint,
|
||||
size: 18,
|
||||
color: isComplete ? AppColors.primary : AppColors.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isComplete ? AppColors.primary : AppColors.onPrimary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onFocusCounterTap(
|
||||
BuildContext context,
|
||||
AppSettings settings,
|
||||
String prefix,
|
||||
List<Map<String, dynamic>> items,
|
||||
) {
|
||||
if (items.isEmpty) return;
|
||||
|
||||
final currentIndex = (_focusPageIndex[prefix] ?? 0).clamp(0, items.length - 1);
|
||||
final item = items[currentIndex];
|
||||
final dzikirId = _resolveDzikirId(item, prefix, currentIndex);
|
||||
final target = (item['ulang'] as num?)?.toInt() ?? 1;
|
||||
|
||||
final becameComplete = _increment(
|
||||
dzikirId,
|
||||
target,
|
||||
hapticEnabled: settings.dzikirHapticOnCount,
|
||||
);
|
||||
|
||||
if (!becameComplete) return;
|
||||
|
||||
final isLast = currentIndex == items.length - 1;
|
||||
if (settings.dzikirAutoAdvance && !isLast) {
|
||||
final controller = _pageControllers[prefix];
|
||||
if (controller != null && controller.hasClients) {
|
||||
controller.nextPage(
|
||||
duration: const Duration(milliseconds: 240),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLast) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Semua dzikir pada tab ini selesai'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _resolveDzikirId(Map<String, dynamic> item, String prefix, int index) {
|
||||
final rawId = item['id']?.toString();
|
||||
if (rawId != null && rawId.isNotEmpty) {
|
||||
return rawId;
|
||||
}
|
||||
return '${prefix}_${index + 1}';
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(
|
||||
bool isDark, {
|
||||
required String title,
|
||||
required String subtitle,
|
||||
}) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.inbox,
|
||||
size: 42,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
223
lib/features/hadits/presentation/hadits_screen.dart
Normal file
@@ -0,0 +1,223 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../../../app/theme/app_colors.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
|
||||
class HaditsScreen extends StatefulWidget {
|
||||
final bool isSimpleModeTab;
|
||||
const HaditsScreen({super.key, this.isSimpleModeTab = false});
|
||||
|
||||
@override
|
||||
State<HaditsScreen> createState() => _HaditsScreenState();
|
||||
}
|
||||
|
||||
class _HaditsScreenState extends State<HaditsScreen> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
List<Map<String, dynamic>> _allHadits = [];
|
||||
List<Map<String, dynamic>> _filteredHadits = [];
|
||||
bool _loading = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadHadits();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadHadits() async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final data = await MuslimApiService.instance.getHaditsList(strict: true);
|
||||
if (!mounted) return;
|
||||
data.sort((a, b) {
|
||||
final aa = (a['no'] as num?)?.toInt() ?? 0;
|
||||
final bb = (b['no'] as num?)?.toInt() ?? 0;
|
||||
return aa.compareTo(bb);
|
||||
});
|
||||
setState(() {
|
||||
_allHadits = data;
|
||||
_filteredHadits = data;
|
||||
_loading = false;
|
||||
});
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_allHadits = [];
|
||||
_filteredHadits = [];
|
||||
_loading = false;
|
||||
_error = 'Gagal memuat hadits dari server';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearchChanged(String value) {
|
||||
final q = value.trim().toLowerCase();
|
||||
if (q.isEmpty) {
|
||||
setState(() => _filteredHadits = _allHadits);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_filteredHadits = _allHadits.where((item) {
|
||||
final title = item['judul']?.toString().toLowerCase() ?? '';
|
||||
final indo = item['indo']?.toString().toLowerCase() ?? '';
|
||||
return title.contains(q) || indo.contains(q);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: !widget.isSimpleModeTab,
|
||||
title: const Text("Hadits Arba'in"),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _loadHadits,
|
||||
icon: const Icon(LucideIcons.refreshCw),
|
||||
tooltip: 'Muat ulang',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
onChanged: _onSearchChanged,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Cari judul atau isi hadits...',
|
||||
prefixIcon: const Icon(LucideIcons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? Center(
|
||||
child: Text(
|
||||
_error!,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: _filteredHadits.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'Hadits tidak ditemukan',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
itemCount: _filteredHadits.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _filteredHadits[index];
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? AppColors.surfaceDark
|
||||
: AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.1)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary
|
||||
.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'${item['no'] ?? '-'}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item['judul']?.toString() ?? '-',
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
item['arab']?.toString() ?? '',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
item['indo']?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,14 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
||||
bool _showLatin = true;
|
||||
bool _showTerjemahan = true;
|
||||
|
||||
String _readingRoute(int surahId, int verseId) {
|
||||
final isSimple =
|
||||
Hive.box<AppSettings>(HiveBoxes.settings).get('default')?.simpleMode ??
|
||||
false;
|
||||
final base = isSimple ? '/quran' : '/tools/quran';
|
||||
return '$base/$surahId?startVerse=$verseId';
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -184,7 +192,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
||||
final dateStr = DateFormat('dd MMM yyyy, HH:mm').format(bookmark.savedAt);
|
||||
|
||||
return InkWell(
|
||||
onTap: () => context.push('/tools/quran/${bookmark.surahId}?startVerse=${bookmark.verseId}'),
|
||||
onTap: () => context.push(_readingRoute(bookmark.surahId, bookmark.verseId)),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -252,6 +260,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
@@ -287,7 +296,8 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => context.push('/tools/quran/${bookmark.surahId}?startVerse=${bookmark.verseId}'),
|
||||
onPressed: () =>
|
||||
context.push(_readingRoute(bookmark.surahId, bookmark.verseId)),
|
||||
icon: const Icon(LucideIcons.bookOpen, size: 18),
|
||||
label: const Text('Lanjutkan Membaca'),
|
||||
style: FilledButton.styleFrom(
|
||||
|
||||
773
lib/features/quran/presentation/quran_enrichment_screen.dart
Normal file
@@ -0,0 +1,773 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../../../app/theme/app_colors.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
|
||||
class QuranEnrichmentScreen extends StatefulWidget {
|
||||
const QuranEnrichmentScreen({super.key});
|
||||
|
||||
@override
|
||||
State<QuranEnrichmentScreen> createState() => _QuranEnrichmentScreenState();
|
||||
}
|
||||
|
||||
class _QuranEnrichmentScreenState extends State<QuranEnrichmentScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final TextEditingController _pageController = TextEditingController(text: '1');
|
||||
|
||||
List<Map<String, dynamic>> _surahs = [];
|
||||
List<Map<String, dynamic>> _searchResults = [];
|
||||
List<Map<String, dynamic>> _tafsirItems = [];
|
||||
List<Map<String, dynamic>> _asbabItems = [];
|
||||
List<Map<String, dynamic>> _juzItems = [];
|
||||
List<Map<String, dynamic>> _pageItems = [];
|
||||
List<Map<String, dynamic>> _themeItems = [];
|
||||
List<Map<String, dynamic>> _asmaItems = [];
|
||||
|
||||
int _selectedSurahId = 1;
|
||||
int _selectedPage = 1;
|
||||
bool _loadingInit = true;
|
||||
bool _loadingSearch = false;
|
||||
bool _loadingTafsir = false;
|
||||
bool _loadingAsbab = false;
|
||||
bool _loadingPage = false;
|
||||
String? _error;
|
||||
|
||||
final Set<String> _expandedWordByWord = {};
|
||||
final Map<String, List<Map<String, dynamic>>> _wordByWord = {};
|
||||
final Set<String> _loadingWordByWord = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 7, vsync: this);
|
||||
_bootstrap();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
_searchController.dispose();
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _bootstrap() async {
|
||||
setState(() {
|
||||
_loadingInit = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final surahs = await MuslimApiService.instance.getAllSurahs();
|
||||
final juz = await MuslimApiService.instance.getJuzList();
|
||||
final themes = await MuslimApiService.instance.getThemes();
|
||||
final asma = await MuslimApiService.instance.getAsmaulHusna();
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_surahs = surahs;
|
||||
_selectedSurahId = surahs.isNotEmpty
|
||||
? ((surahs.first['nomor'] as int?) ?? 1)
|
||||
: 1;
|
||||
_juzItems = juz;
|
||||
_themeItems = themes;
|
||||
_asmaItems = asma;
|
||||
_loadingInit = false;
|
||||
});
|
||||
|
||||
await _loadTafsirForSelectedSurah();
|
||||
await _loadAsbabForSelectedSurah();
|
||||
await _loadPageAyah();
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_loadingInit = false;
|
||||
_error = 'Gagal memuat data enrichment';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runSearch() async {
|
||||
final query = _searchController.text.trim();
|
||||
if (query.isEmpty) {
|
||||
setState(() => _searchResults = []);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _loadingSearch = true);
|
||||
final result = await MuslimApiService.instance.searchAyah(query);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_searchResults = result;
|
||||
_loadingSearch = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadTafsirForSelectedSurah() async {
|
||||
setState(() => _loadingTafsir = true);
|
||||
final result =
|
||||
await MuslimApiService.instance.getTafsirBySurah(_selectedSurahId);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_tafsirItems = result;
|
||||
_loadingTafsir = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadAsbabForSelectedSurah() async {
|
||||
setState(() => _loadingAsbab = true);
|
||||
final result = await MuslimApiService.instance.getAsbabBySurah(_selectedSurahId);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_asbabItems = result;
|
||||
_loadingAsbab = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadPageAyah() async {
|
||||
setState(() => _loadingPage = true);
|
||||
final page = int.tryParse(_pageController.text.trim()) ?? _selectedPage;
|
||||
final safePage = page.clamp(1, 604);
|
||||
final result = await MuslimApiService.instance.getAyahByPage(safePage);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_selectedPage = safePage;
|
||||
_pageController.text = '$safePage';
|
||||
_pageItems = result;
|
||||
_loadingPage = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _toggleWordByWord(Map<String, dynamic> ayah) async {
|
||||
final surah = (ayah['surah'] as num?)?.toInt();
|
||||
final ayahNum = (ayah['ayah'] as num?)?.toInt();
|
||||
if (surah == null || ayahNum == null) return;
|
||||
|
||||
final key = '$surah:$ayahNum';
|
||||
final expanded = _expandedWordByWord.contains(key);
|
||||
|
||||
if (expanded) {
|
||||
setState(() => _expandedWordByWord.remove(key));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_wordByWord.containsKey(key)) {
|
||||
setState(() => _expandedWordByWord.add(key));
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_loadingWordByWord.add(key);
|
||||
_expandedWordByWord.add(key);
|
||||
});
|
||||
|
||||
final words = await MuslimApiService.instance.getWordByWord(surah, ayahNum);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_wordByWord[key] = words;
|
||||
_loadingWordByWord.remove(key);
|
||||
});
|
||||
}
|
||||
|
||||
String _surahNameById(int surahId) {
|
||||
for (final s in _surahs) {
|
||||
if (s['nomor'] == surahId) {
|
||||
return s['namaLatin']?.toString() ?? 'Surah $surahId';
|
||||
}
|
||||
}
|
||||
return 'Surah $surahId';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Quran Enrichment'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _bootstrap,
|
||||
icon: const Icon(LucideIcons.refreshCw),
|
||||
tooltip: 'Muat ulang',
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: true,
|
||||
labelColor: AppColors.primary,
|
||||
unselectedLabelColor: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
indicatorColor: AppColors.primary,
|
||||
tabs: const [
|
||||
Tab(text: 'Cari'),
|
||||
Tab(text: 'Tafsir'),
|
||||
Tab(text: 'Asbab'),
|
||||
Tab(text: 'Juz'),
|
||||
Tab(text: 'Halaman'),
|
||||
Tab(text: 'Tema'),
|
||||
Tab(text: 'Asmaul Husna'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: _loadingInit
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? Center(
|
||||
child: Text(
|
||||
_error!,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildSearchTab(context, isDark),
|
||||
_buildTafsirTab(context, isDark),
|
||||
_buildAsbabTab(context, isDark),
|
||||
_buildJuzTab(context, isDark),
|
||||
_buildPageTab(context, isDark),
|
||||
_buildThemeTab(context, isDark),
|
||||
_buildAsmaTab(context, isDark),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchTab(BuildContext context, bool isDark) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: (_) => _runSearch(),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Cari ayat, tema, atau kata kunci...',
|
||||
prefixIcon: const Icon(LucideIcons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: _runSearch,
|
||||
child: const Text('Cari'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _loadingSearch
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _searchResults.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'Belum ada hasil pencarian',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
itemCount: _searchResults.length,
|
||||
itemBuilder: (context, index) {
|
||||
final ayah = _searchResults[index];
|
||||
final surahId = (ayah['surah'] as num?)?.toInt() ?? 0;
|
||||
final ayahNum = (ayah['ayah'] as num?)?.toInt() ?? 0;
|
||||
final key = '$surahId:$ayahNum';
|
||||
final expanded = _expandedWordByWord.contains(key);
|
||||
final words = _wordByWord[key] ?? const [];
|
||||
final loadingWords = _loadingWordByWord.contains(key);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? AppColors.surfaceDark
|
||||
: AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.1)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${_surahNameById(surahId)} : $ayahNum',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () => _toggleWordByWord(ayah),
|
||||
icon: Icon(
|
||||
expanded
|
||||
? LucideIcons.chevronUp
|
||||
: LucideIcons.languages,
|
||||
size: 16,
|
||||
),
|
||||
label: Text(
|
||||
expanded ? 'Tutup' : 'Per Kata',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
ayah['arab']?.toString() ?? '',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
ayah['text']?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
if (expanded) ...[
|
||||
const SizedBox(height: 12),
|
||||
if (loadingWords)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
else if (words.isEmpty)
|
||||
Text(
|
||||
'Data kata tidak tersedia untuk ayat ini.',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
)
|
||||
else
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: words.map((word) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary
|
||||
.withValues(alpha: 0.08),
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
word['arab']?.toString() ?? '',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
word['word']?.toString() ?? '',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
word['indo']?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: isDark
|
||||
? AppColors
|
||||
.textSecondaryDark
|
||||
: AppColors
|
||||
.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTafsirTab(BuildContext context, bool isDark) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildSurahSelector(
|
||||
onChanged: (value) {
|
||||
setState(() => _selectedSurahId = value);
|
||||
_loadTafsirForSelectedSurah();
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: _loadingTafsir
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _tafsirItems.isEmpty
|
||||
? _emptyText(isDark, 'Belum ada data tafsir untuk surah ini')
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
itemCount: _tafsirItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _tafsirItems[index];
|
||||
final ayah = item['nomorAyat']?.toString() ?? '-';
|
||||
final wajiz = item['wajiz']?.toString() ?? '';
|
||||
final tahlili = item['tahlili']?.toString() ?? '';
|
||||
return _buildCard(
|
||||
isDark,
|
||||
title: 'Ayat $ayah',
|
||||
body: '$wajiz\n\n$tahlili',
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAsbabTab(BuildContext context, bool isDark) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildSurahSelector(
|
||||
onChanged: (value) {
|
||||
setState(() => _selectedSurahId = value);
|
||||
_loadAsbabForSelectedSurah();
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: _loadingAsbab
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _asbabItems.isEmpty
|
||||
? _emptyText(
|
||||
isDark,
|
||||
'Belum ada data asbabun nuzul untuk surah ini',
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
itemCount: _asbabItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _asbabItems[index];
|
||||
final ayah = item['nomorAyat']?.toString() ?? '-';
|
||||
return _buildCard(
|
||||
isDark,
|
||||
title: 'Ayat $ayah',
|
||||
body: item['text']?.toString() ?? '',
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJuzTab(BuildContext context, bool isDark) {
|
||||
if (_juzItems.isEmpty) {
|
||||
return _emptyText(isDark, 'Data juz tidak tersedia');
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
|
||||
itemCount: _juzItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _juzItems[index];
|
||||
final number = item['number']?.toString() ?? '-';
|
||||
final startName = item['name_start_id']?.toString() ?? '-';
|
||||
final endName = item['name_end_id']?.toString() ?? '-';
|
||||
final startVerse = item['verse_start']?.toString() ?? '-';
|
||||
final endVerse = item['verse_end']?.toString() ?? '-';
|
||||
|
||||
return _buildCard(
|
||||
isDark,
|
||||
title: 'Juz $number',
|
||||
body:
|
||||
'Mulai: $startName ayat $startVerse\nSelesai: $endName ayat $endVerse',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageTab(BuildContext context, bool isDark) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _pageController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nomor Halaman (1-604)',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: _loadPageAyah,
|
||||
child: const Text('Tampilkan'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _loadingPage
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _pageItems.isEmpty
|
||||
? _emptyText(isDark, 'Tidak ada data untuk halaman ini')
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
itemCount: _pageItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _pageItems[index];
|
||||
final surahId = (item['surah'] as num?)?.toInt() ?? 0;
|
||||
final ayah = item['ayah']?.toString() ?? '-';
|
||||
|
||||
return _buildCard(
|
||||
isDark,
|
||||
title: '${_surahNameById(surahId)} : $ayah',
|
||||
body:
|
||||
'${item['arab']?.toString() ?? ''}\n\n${item['text']?.toString() ?? ''}',
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildThemeTab(BuildContext context, bool isDark) {
|
||||
if (_themeItems.isEmpty) {
|
||||
return _emptyText(isDark, 'Data tema belum tersedia');
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
|
||||
itemCount: _themeItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _themeItems[index];
|
||||
return _buildCard(
|
||||
isDark,
|
||||
title: 'Tema #${item['id'] ?? '-'}',
|
||||
body: item['name']?.toString() ?? '',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAsmaTab(BuildContext context, bool isDark) {
|
||||
if (_asmaItems.isEmpty) {
|
||||
return _emptyText(isDark, 'Data Asmaul Husna tidak tersedia');
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
|
||||
itemCount: _asmaItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _asmaItems[index];
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.1)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'${item['id'] ?? '-'}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item['arab']?.toString() ?? '',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item['latin']?.toString() ?? '',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item['indo']?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSurahSelector({required ValueChanged<int> onChanged}) {
|
||||
if (_surahs.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: _emptyText(
|
||||
Theme.of(context).brightness == Brightness.dark,
|
||||
'Data surah tidak tersedia',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.primary.withValues(alpha: 0.2)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<int>(
|
||||
value: _selectedSurahId,
|
||||
isExpanded: true,
|
||||
items: _surahs.map((surah) {
|
||||
final id = (surah['nomor'] as num?)?.toInt() ?? 1;
|
||||
final name = surah['namaLatin']?.toString() ?? 'Surah $id';
|
||||
return DropdownMenuItem<int>(
|
||||
value: id,
|
||||
child: Text('$id. $name'),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
onChanged(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(bool isDark, {required String title, required String body}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.1)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(body, style: const TextStyle(height: 1.5)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _emptyText(bool isDark, String text) {
|
||||
return Center(
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,11 @@ import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../app/theme/app_colors.dart';
|
||||
import '../../../data/services/equran_service.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
import '../../../data/services/unsplash_service.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import '../../../data/local/hive_boxes.dart';
|
||||
import '../../../data/local/models/app_settings.dart';
|
||||
|
||||
/// Quran Murattal (audio player) screen.
|
||||
/// Implements full Surah playback using just_audio and EQuran v2 API.
|
||||
/// Implements full Surah playback using just_audio.
|
||||
class QuranMurattalScreen extends ConsumerStatefulWidget {
|
||||
final String surahId;
|
||||
final String? initialQariId;
|
||||
@@ -77,7 +74,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
|
||||
Future<void> _initDataAndPlayer() async {
|
||||
final surahNum = int.tryParse(widget.surahId) ?? 1;
|
||||
final data = await EQuranService.instance.getSurah(surahNum);
|
||||
final data = await MuslimApiService.instance.getSurah(surahNum);
|
||||
|
||||
if (data != null && mounted) {
|
||||
setState(() {
|
||||
@@ -186,7 +183,10 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
|
||||
void _navigateToSurahNumber(int surahNum, {bool autoplay = false}) {
|
||||
if (surahNum >= 1 && surahNum <= 114) {
|
||||
context.pushReplacement('/tools/quran/$surahNum/murattal?qariId=$_selectedQariId&autoplay=$autoplay');
|
||||
final base = widget.isSimpleModeTab ? '/quran' : '/tools/quran';
|
||||
context.pushReplacement(
|
||||
'$base/$surahNum/murattal?qariId=$_selectedQariId&autoplay=$autoplay',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...EQuranService.qariNames.entries.map((entry) {
|
||||
...MuslimApiService.qariNames.entries.map((entry) {
|
||||
final isSelected = entry.key == _selectedQariId;
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
@@ -287,7 +287,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: EQuranService.instance.getAllSurahs(),
|
||||
future: MuslimApiService.instance.getAllSurahs(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@@ -339,7 +339,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
Navigator.pop(context);
|
||||
if (!isCurrentSurah) {
|
||||
context.pushReplacement(
|
||||
'/tools/quran/$surahNum/murattal?qariId=$_selectedQariId',
|
||||
'${widget.isSimpleModeTab ? '/quran' : '/tools/quran'}/$surahNum/murattal?qariId=$_selectedQariId',
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -360,8 +360,6 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final box = Hive.box<AppSettings>(HiveBoxes.settings);
|
||||
final isSimpleMode = box.get('default')?.simpleMode ?? false;
|
||||
final surahName = _surahData?['namaLatin'] ?? 'Surah ${widget.surahId}';
|
||||
|
||||
final hasPhoto = _unsplashPhoto != null;
|
||||
@@ -519,7 +517,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
const SizedBox(height: 32),
|
||||
// Qari name
|
||||
Text(
|
||||
EQuranService.qariNames[_selectedQariId] ?? 'Memuat...',
|
||||
MuslimApiService.qariNames[_selectedQariId] ?? 'Memuat...',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -742,7 +740,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
||||
color: _unsplashPhoto != null ? Colors.white : AppColors.primary),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
EQuranService.qariNames[_selectedQariId] ?? 'Ganti Qari',
|
||||
MuslimApiService.qariNames[_selectedQariId] ?? 'Ganti Qari',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@@ -12,7 +12,7 @@ import '../../../data/local/models/quran_bookmark.dart';
|
||||
import '../../../data/local/models/app_settings.dart';
|
||||
import '../../../data/local/models/daily_worship_log.dart';
|
||||
import '../../../data/local/models/tilawah_log.dart';
|
||||
import '../../../data/services/equran_service.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
import '../../../core/providers/tilawah_tracking_provider.dart';
|
||||
|
||||
class QuranReadingScreen extends ConsumerStatefulWidget {
|
||||
@@ -151,7 +151,8 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
||||
|
||||
Future<void> _loadSurah() async {
|
||||
final surahNum = int.tryParse(widget.surahId) ?? 1;
|
||||
final data = await EQuranService.instance.getSurah(surahNum);
|
||||
final data = await MuslimApiService.instance.getSurah(surahNum);
|
||||
if (!mounted) return;
|
||||
if (data != null) {
|
||||
setState(() {
|
||||
_surah = data;
|
||||
@@ -356,7 +357,9 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
} Future<void> _showEndTrackingDialog(TilawahSession session, int endVerseId) async {
|
||||
}
|
||||
|
||||
Future<void> _showEndTrackingDialog(TilawahSession session, int endVerseId) async {
|
||||
final endSurahId = _surah!['nomor'] ?? 1;
|
||||
final endSurahName = _surah!['namaLatin'] ?? '';
|
||||
|
||||
@@ -367,26 +370,30 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
||||
calculatedAyat = (endVerseId - session.startVerseId).abs() + 1;
|
||||
} else {
|
||||
// Cross surah calculation
|
||||
final allSurahs = await EQuranService.instance.getAllSurahs();
|
||||
final allSurahs = await MuslimApiService.instance.getAllSurahs();
|
||||
if (allSurahs.isNotEmpty) {
|
||||
int startSurahIdx = allSurahs.indexWhere((s) => s['nomor'] == session.startSurahId);
|
||||
int endSurahIdx = allSurahs.indexWhere((s) => s['nomor'] == endSurahId);
|
||||
|
||||
// Ensure chronological calculation
|
||||
if (startSurahIdx > endSurahIdx) {
|
||||
final tempIdx = startSurahIdx; startSurahIdx = endSurahIdx; endSurahIdx = tempIdx;
|
||||
|
||||
if (startSurahIdx < 0 || endSurahIdx < 0) {
|
||||
calculatedAyat = (endVerseId - session.startVerseId).abs() + 1;
|
||||
} else {
|
||||
// Ensure chronological calculation
|
||||
if (startSurahIdx > endSurahIdx) {
|
||||
final tempIdx = startSurahIdx; startSurahIdx = endSurahIdx; endSurahIdx = tempIdx;
|
||||
}
|
||||
|
||||
final startSurahData = allSurahs[startSurahIdx];
|
||||
final int totalAyatInStart = (startSurahData['jumlahAyat'] as num?)?.toInt() ?? 1;
|
||||
|
||||
calculatedAyat += (totalAyatInStart - session.startVerseId) + 1; // Ayats inside StartSurah
|
||||
|
||||
for (int i = startSurahIdx + 1; i < endSurahIdx; i++) {
|
||||
calculatedAyat += (allSurahs[i]['jumlahAyat'] as int? ?? 0); // Intermediate Surahs
|
||||
}
|
||||
|
||||
calculatedAyat += endVerseId; // Ayats inside EndSurah
|
||||
}
|
||||
|
||||
final startSurahData = allSurahs[startSurahIdx];
|
||||
final int totalAyatInStart = (startSurahData['jumlahAyat'] as num?)?.toInt() ?? 1;
|
||||
|
||||
calculatedAyat += (totalAyatInStart - session.startVerseId) + 1; // Ayats inside StartSurah
|
||||
|
||||
for (int i = startSurahIdx + 1; i < endSurahIdx; i++) {
|
||||
calculatedAyat += (allSurahs[i]['jumlahAyat'] as int? ?? 0); // Intermediate Surahs
|
||||
}
|
||||
|
||||
calculatedAyat += endVerseId; // Ayats inside EndSurah
|
||||
} else {
|
||||
calculatedAyat = 1; // Fallback
|
||||
}
|
||||
@@ -572,6 +579,11 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(LucideIcons.headphones),
|
||||
tooltip: 'Murattal Surah',
|
||||
onPressed: _navigateToMurattal,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
LucideIcons.brain,
|
||||
@@ -620,6 +632,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
||||
style: TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -802,6 +815,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 2.0,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../../../app/theme/app_colors.dart';
|
||||
import '../../../data/local/hive_boxes.dart';
|
||||
import '../../../data/local/models/app_settings.dart';
|
||||
import '../../../data/local/models/quran_bookmark.dart';
|
||||
import '../../../data/services/equran_service.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
|
||||
class QuranScreen extends ConsumerStatefulWidget {
|
||||
final bool isSimpleModeTab;
|
||||
@@ -36,7 +36,8 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadSurahs() async {
|
||||
final data = await EQuranService.instance.getAllSurahs();
|
||||
final data = await MuslimApiService.instance.getAllSurahs();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_surahs = data;
|
||||
_loading = false;
|
||||
@@ -100,8 +101,6 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final box = Hive.box<AppSettings>(HiveBoxes.settings);
|
||||
final isSimpleMode = box.get('default')?.simpleMode ?? false;
|
||||
final filtered = _searchQuery.isEmpty
|
||||
? _surahs
|
||||
: _surahs
|
||||
@@ -119,7 +118,15 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(LucideIcons.bookmark),
|
||||
onPressed: () => context.push('/tools/quran/bookmarks'),
|
||||
onPressed: () => context.push(widget.isSimpleModeTab
|
||||
? '/quran/bookmarks'
|
||||
: '/tools/quran/bookmarks'),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(LucideIcons.sparkles),
|
||||
onPressed: () => context.push(widget.isSimpleModeTab
|
||||
? '/quran/enrichment'
|
||||
: '/tools/quran/enrichment'),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(LucideIcons.settings2),
|
||||
@@ -198,8 +205,9 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
||||
final hasLastRead = box.values.any((b) => b.isLastRead && b.surahId == number);
|
||||
|
||||
return ListTile(
|
||||
onTap: () =>
|
||||
context.push('/tools/quran/$number'),
|
||||
onTap: () => context.push(widget.isSimpleModeTab
|
||||
? '/quran/$number'
|
||||
: '/tools/quran/$number'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 0, vertical: 6),
|
||||
leading: Container(
|
||||
@@ -250,6 +258,7 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -244,6 +244,70 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ── DZIKIR DISPLAY ──
|
||||
_sectionLabel('TAMPILAN DZIKIR'),
|
||||
const SizedBox(height: 12),
|
||||
_buildSegmentSettingCard(
|
||||
isDark,
|
||||
title: 'Mode Tampilan Dzikir',
|
||||
subtitle: 'Pilih daftar baris atau fokus per slide',
|
||||
value: _settings.dzikirDisplayMode,
|
||||
options: const {
|
||||
'list': 'Daftar (Baris)',
|
||||
'focus': 'Fokus (Slide)',
|
||||
},
|
||||
onChanged: (value) {
|
||||
_settings.dzikirDisplayMode = value;
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
if (_settings.dzikirDisplayMode == 'focus') ...[
|
||||
const SizedBox(height: 10),
|
||||
_buildSegmentSettingCard(
|
||||
isDark,
|
||||
title: 'Posisi Tombol Hitung',
|
||||
subtitle: 'Atur posisi tombol pada mode fokus',
|
||||
value: _settings.dzikirCounterButtonPosition,
|
||||
options: const {
|
||||
'bottomPill': 'Pill Bawah',
|
||||
'fabCircle': 'Bulat Kanan Bawah',
|
||||
},
|
||||
onChanged: (value) {
|
||||
_settings.dzikirCounterButtonPosition = value;
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_settingRow(
|
||||
isDark,
|
||||
icon: LucideIcons.arrowRight,
|
||||
iconColor: const Color(0xFF00B894),
|
||||
title: 'Lanjut Otomatis Saat Target Tercapai',
|
||||
trailing: IosToggle(
|
||||
value: _settings.dzikirAutoAdvance,
|
||||
onChanged: (v) {
|
||||
_settings.dzikirAutoAdvance = v;
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
_settingRow(
|
||||
isDark,
|
||||
icon: LucideIcons.vibrate,
|
||||
iconColor: const Color(0xFF6C5CE7),
|
||||
title: 'Getaran Saat Hitung',
|
||||
trailing: IosToggle(
|
||||
value: _settings.dzikirHapticOnCount,
|
||||
onChanged: (v) {
|
||||
_settings.dzikirHapticOnCount = v;
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ── PRAYER SETTINGS ──
|
||||
_sectionLabel('WAKTU SHOLAT'),
|
||||
const SizedBox(height: 12),
|
||||
@@ -438,6 +502,103 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSegmentSettingCard(
|
||||
bool isDark, {
|
||||
required String title,
|
||||
String? subtitle,
|
||||
required String value,
|
||||
required Map<String, String> options,
|
||||
required ValueChanged<String> onChanged,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.08)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? AppColors.backgroundDark
|
||||
: AppColors.backgroundLight,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.08)
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: options.entries.map((entry) {
|
||||
final selected = value == entry.key;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => onChanged(entry.key),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 160),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? AppColors.primary
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
entry.value,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: selected
|
||||
? AppColors.onPrimary
|
||||
: (isDark
|
||||
? AppColors.textPrimaryDark
|
||||
: AppColors.textPrimaryLight),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMethodDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../../../app/theme/app_colors.dart';
|
||||
import '../../../core/widgets/tool_card.dart';
|
||||
import '../../../data/services/equran_service.dart';
|
||||
import '../../../data/services/muslim_api_service.dart';
|
||||
|
||||
class ToolsScreen extends ConsumerWidget {
|
||||
const ToolsScreen({super.key});
|
||||
@@ -29,7 +29,7 @@ class ToolsScreen extends ConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -50,9 +50,9 @@ class ToolsScreen extends ConsumerWidget {
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.bookOpen,
|
||||
title: 'Al-Quran\nTerjemahan',
|
||||
color: const Color(0xFF00b894),
|
||||
color: const Color(0xFF00B894),
|
||||
isDark: isDark,
|
||||
onTap: () => context.push('/quran'),
|
||||
onTap: () => context.push('/tools/quran'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -62,7 +62,7 @@ class ToolsScreen extends ConsumerWidget {
|
||||
title: 'Quran\nMurattal',
|
||||
color: const Color(0xFF7B61FF),
|
||||
isDark: isDark,
|
||||
onTap: () => context.push('/quran/1/murattal'),
|
||||
onTap: () => context.push('/tools/quran/1/murattal'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -83,25 +83,65 @@ class ToolsScreen extends ConsumerWidget {
|
||||
Expanded(
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.sparkles,
|
||||
title: 'Tasbih\nDigital',
|
||||
title: 'Dzikir\nHarian',
|
||||
color: AppColors.primary,
|
||||
isDark: isDark,
|
||||
onTap: () => context.push('/dzikir'),
|
||||
onTap: () => context.push('/tools/dzikir'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Ayat Hari Ini
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.heart,
|
||||
title: 'Kumpulan\nDoa',
|
||||
color: const Color(0xFFE17055),
|
||||
isDark: isDark,
|
||||
onTap: () => context.push('/tools/doa'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.library,
|
||||
title: "Hadits\nArba'in",
|
||||
color: const Color(0xFF6C5CE7),
|
||||
isDark: isDark,
|
||||
onTap: () => context.push('/tools/hadits'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ToolCard(
|
||||
icon: LucideIcons.sparkles,
|
||||
title: 'Quran\nEnrichment',
|
||||
color: const Color(0xFF00CEC9),
|
||||
isDark: isDark,
|
||||
onTap: () => context.push('/tools/quran/enrichment'),
|
||||
),
|
||||
),
|
||||
const Expanded(child: SizedBox()),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
FutureBuilder<Map<String, dynamic>?>(
|
||||
future: EQuranService.instance.getDailyAyat(),
|
||||
future: MuslimApiService.instance.getDailyAyat(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.primary.withValues(alpha: 0.08) : const Color(0xFFF5F9F0),
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.08)
|
||||
: const Color(0xFFF5F9F0),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
@@ -109,7 +149,7 @@ class ToolsScreen extends ConsumerWidget {
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data == null) {
|
||||
return const SizedBox.shrink(); // Hide if error/no internet
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final data = snapshot.data!;
|
||||
@@ -117,7 +157,9 @@ class ToolsScreen extends ConsumerWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.primary.withValues(alpha: 0.08) : const Color(0xFFF5F9F0),
|
||||
color: isDark
|
||||
? AppColors.primary.withValues(alpha: 0.08)
|
||||
: const Color(0xFFF5F9F0),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
@@ -131,13 +173,19 @@ class ToolsScreen extends ConsumerWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(LucideIcons.share2,
|
||||
size: 18,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight),
|
||||
icon: Icon(
|
||||
LucideIcons.share2,
|
||||
size: 18,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
@@ -150,6 +198,7 @@ class ToolsScreen extends ConsumerWidget {
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Amiri',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.8,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
|
||||
107
logo-luxury-theme-brief.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Logo Palette + Luxury Active Menu Brief
|
||||
|
||||
## 1) Objective
|
||||
Adopt the app's visual identity from the logo palette (teal + gold) and introduce a luxury-feel active menu state for both dark and light themes.
|
||||
|
||||
Key intent:
|
||||
- Brand-consistent color system.
|
||||
- Premium, elegant active menu treatment.
|
||||
- Subtle animated gold shine (not flashy, not distracting).
|
||||
|
||||
## 2) Design Direction
|
||||
|
||||
### Brand mood
|
||||
- Calm spiritual base: teal tones.
|
||||
- Premium emphasis: gold used only for highlights/active states.
|
||||
- Minimal and clean surfaces, with controlled depth (soft shadow + thin stroke).
|
||||
|
||||
### Usage rule
|
||||
- Teal = foundation color.
|
||||
- Gold = premium interaction color (active nav, selected/high-priority accents).
|
||||
- Avoid using gold as the global background.
|
||||
|
||||
## 3) Proposed Color Tokens
|
||||
|
||||
These are starting values to tune after visual QA:
|
||||
|
||||
### Core brand tokens
|
||||
- `brand.teal.500`: `#118A8D`
|
||||
- `brand.teal.700`: `#0C676A`
|
||||
- `brand.teal.900`: `#0A4447`
|
||||
- `brand.gold.400`: `#D6A21D`
|
||||
- `brand.gold.300`: `#E9C75B`
|
||||
- `brand.gold.200`: `#F6DE96`
|
||||
- `brand.gold.700`: `#8B6415`
|
||||
|
||||
### Dark theme base
|
||||
- `bg.dark`: `#0F1217`
|
||||
- `surface.dark`: `#171B22`
|
||||
- `surface.dark.elevated`: `#1D222B`
|
||||
- `text.dark.primary`: `#E8ECF2`
|
||||
- `text.dark.secondary`: `#9AA4B2`
|
||||
|
||||
### Light theme base
|
||||
- `bg.light`: `#F3F4F6`
|
||||
- `surface.light`: `#FFFFFF`
|
||||
- `surface.light.elevated`: `#F9FAFB`
|
||||
- `text.light.primary`: `#1F2937`
|
||||
- `text.light.secondary`: `#6B7280`
|
||||
|
||||
## 4) Active Menu Visual Spec
|
||||
|
||||
### Shape and structure
|
||||
- Active menu item uses rounded-square container.
|
||||
- Ring consists of:
|
||||
- Inner metallic gold stroke.
|
||||
- Soft outer glow (stronger in dark mode, lighter in light mode).
|
||||
- Inactive items remain neutral gray/secondary text.
|
||||
|
||||
### Gold ring treatment
|
||||
- Gradient on ring: dark-gold -> bright-gold -> pale-gold -> dark-gold.
|
||||
- Keep ring thin and crisp (avoid thick glowing halo).
|
||||
- Shine highlight should pass around the ring edge only.
|
||||
|
||||
### Light mode adaptation
|
||||
- Reduce glow blur and opacity by ~35-45% vs dark mode.
|
||||
- Add subtle neutral shadow to preserve depth on bright surfaces.
|
||||
|
||||
## 5) Motion Spec (Shine Animation)
|
||||
|
||||
- Animation target: active menu ring only.
|
||||
- Loop duration: `2.8s-3.6s`.
|
||||
- Shine pass visibility: short burst (`~700-900ms`) then calm period.
|
||||
- Easing: smooth in/out (no linear harsh movement).
|
||||
- Keep animation subtle enough to avoid drawing attention from content.
|
||||
|
||||
Reduced motion behavior:
|
||||
- If reduced motion is enabled, disable moving shine and keep static gold ring.
|
||||
|
||||
## 6) Accessibility & Quality Constraints
|
||||
|
||||
- Maintain icon/text contrast minimum:
|
||||
- Non-text UI/icon target: at least `3:1`.
|
||||
- Small text target: at least `4.5:1`.
|
||||
- Gold accents must not reduce legibility.
|
||||
- Active state must remain distinguishable in both themes without relying only on color.
|
||||
|
||||
## 7) Product Rules
|
||||
|
||||
- Apply luxury active style only to active navigation state (not every component).
|
||||
- Keep one source of truth for tokens across screens.
|
||||
- Preserve current interaction speed and perceived responsiveness.
|
||||
|
||||
## 8) Rollout Plan
|
||||
|
||||
1. Approve token palette on static mockups (dark + light).
|
||||
2. Apply to one pilot component: bottom navigation active item.
|
||||
3. Validate contrast and motion comfort.
|
||||
4. Roll out to other selected active states (tabs/chips), not all controls.
|
||||
|
||||
## 9) Acceptance Criteria
|
||||
|
||||
- Brand identity clearly reflects logo colors.
|
||||
- Active menu looks premium in dark and light mode.
|
||||
- Shine animation feels elegant and subtle, never distracting.
|
||||
- No readability regressions.
|
||||
- Reduced-motion path is supported.
|
||||
|
||||
@@ -38,5 +38,16 @@ end
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15'
|
||||
if target.name == 'audio_session'
|
||||
warning_flags = config.build_settings['WARNING_CFLAGS'] || '$(inherited)'
|
||||
warning_flags = [warning_flags] unless warning_flags.is_a?(Array)
|
||||
unless warning_flags.include?('-Wno-unused-value')
|
||||
warning_flags << '-Wno-unused-value'
|
||||
end
|
||||
config.build_settings['WARNING_CFLAGS'] = warning_flags
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,16 +53,16 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
audio_service: cab6c1a0eaf01b5a35b567e11fa67d3cc1956910
|
||||
audio_session: 728ae3823d914f809c485d390274861a24b0904e
|
||||
flutter_local_notifications: 14e285ca39907db50704f7f46c9ab7a526bd7ead
|
||||
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
|
||||
audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e
|
||||
flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd
|
||||
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
|
||||
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
url_launcher_macos: 175a54c831f4375a6cf895875f716ee5af3888ce
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
PODFILE CHECKSUM: e84c52ef5d3a8e77f70c2a1d22c490d3e6258427
|
||||
|
||||
COCOAPODS: 1.12.0
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0C90C3ED62E3E14394A23EE5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A4B57B5283BA4F62AC20241 /* Pods_Runner.framework */; };
|
||||
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 */; };
|
||||
DDE68F59044EBC73D03E0962 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DF3757EFF54A1EC85BA5E22 /* Pods_RunnerTests.framework */; };
|
||||
41981E14C9DB7600396CF65B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0696C611C50F37ED234934AD /* Pods_Runner.framework */; };
|
||||
E4654D13A28FD9D97A9BEAB5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEFCC482F4CDCD13DE76DF7 /* Pods_RunnerTests.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -62,11 +62,9 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00FDE0E819DF753D953FEBB2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
06DAA92E91CF7851957A0E28 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
07D5D0934671F750DA630F1D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
0BB6B1FDF75FA1C8F8165DEB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
1F212DF96DD0BB5851DDFC62 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0696C611C50F37ED234934AD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0EA81656FD366AC44330F725 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
2177AABE95008D4FE1E5242D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -83,11 +81,13 @@
|
||||
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>"; };
|
||||
6A4B57B5283BA4F62AC20241 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4AA0251CD9D710A50D527654 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
7DF3757EFF54A1EC85BA5E22 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
E6FD2B4E523D8881848DBBE0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
9AEFCC482F4CDCD13DE76DF7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A59D8BEEC1995674B843D406 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
CEFABEB8B68EDF9F2E4321B2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
F8D884B7ADEE919A7ABF960E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -95,7 +95,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDE68F59044EBC73D03E0962 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
E4654D13A28FD9D97A9BEAB5 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -103,27 +103,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0C90C3ED62E3E14394A23EE5 /* Pods_Runner.framework in Frameworks */,
|
||||
41981E14C9DB7600396CF65B /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0534C72D1883289C9A7D2A97 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0BB6B1FDF75FA1C8F8165DEB /* Pods-Runner.debug.xcconfig */,
|
||||
07D5D0934671F750DA630F1D /* Pods-Runner.release.xcconfig */,
|
||||
00FDE0E819DF753D953FEBB2 /* Pods-Runner.profile.xcconfig */,
|
||||
1F212DF96DD0BB5851DDFC62 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
06DAA92E91CF7851957A0E28 /* Pods-RunnerTests.release.xcconfig */,
|
||||
E6FD2B4E523D8881848DBBE0 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -150,8 +136,8 @@
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
0534C72D1883289C9A7D2A97 /* Pods */,
|
||||
B1CD7E90020BCFEFA243FA5B /* Pods */,
|
||||
C5836D92F95B6223EA2AB79E /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -199,11 +185,25 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
B1CD7E90020BCFEFA243FA5B /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A4B57B5283BA4F62AC20241 /* Pods_Runner.framework */,
|
||||
7DF3757EFF54A1EC85BA5E22 /* Pods_RunnerTests.framework */,
|
||||
A59D8BEEC1995674B843D406 /* Pods-Runner.debug.xcconfig */,
|
||||
2177AABE95008D4FE1E5242D /* Pods-Runner.release.xcconfig */,
|
||||
F8D884B7ADEE919A7ABF960E /* Pods-Runner.profile.xcconfig */,
|
||||
CEFABEB8B68EDF9F2E4321B2 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
0EA81656FD366AC44330F725 /* Pods-RunnerTests.release.xcconfig */,
|
||||
4AA0251CD9D710A50D527654 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C5836D92F95B6223EA2AB79E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0696C611C50F37ED234934AD /* Pods_Runner.framework */,
|
||||
9AEFCC482F4CDCD13DE76DF7 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -215,7 +215,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
9B95A5421B9DABEF4DB6101B /* [CP] Check Pods Manifest.lock */,
|
||||
CF2BEA357E30795FE78CD469 /* [CP] Check Pods Manifest.lock */,
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
@@ -234,13 +234,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
2DC954CD4D756DB6686B4570 /* [CP] Check Pods Manifest.lock */,
|
||||
379A5F95EEFA0001FF3150C5 /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
7C73BC09BD737FBB6206AB8D /* [CP] Embed Pods Frameworks */,
|
||||
080782848EF12E763C1C7A22 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -323,26 +323,21 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
2DC954CD4D756DB6686B4570 /* [CP] Check Pods Manifest.lock */ = {
|
||||
080782848EF12E763C1C7A22 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
@@ -383,24 +378,29 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
7C73BC09BD737FBB6206AB8D /* [CP] Embed Pods Frameworks */ = {
|
||||
379A5F95EEFA0001FF3150C5 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9B95A5421B9DABEF4DB6101B /* [CP] Check Pods Manifest.lock */ = {
|
||||
CF2BEA357E30795FE78CD469 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -473,7 +473,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 1F212DF96DD0BB5851DDFC62 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
baseConfigurationReference = CEFABEB8B68EDF9F2E4321B2 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -488,7 +488,7 @@
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 06DAA92E91CF7851957A0E28 /* Pods-RunnerTests.release.xcconfig */;
|
||||
baseConfigurationReference = 0EA81656FD366AC44330F725 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -503,7 +503,7 @@
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E6FD2B4E523D8881848DBBE0 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
baseConfigurationReference = 4AA0251CD9D710A50D527654 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 616 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |