feat: checkpoint API migration and dzikir UX updates
@@ -18,6 +18,12 @@ migration:
|
|||||||
- platform: android
|
- platform: android
|
||||||
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||||
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||||
|
- platform: ios
|
||||||
|
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||||
|
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||||
|
- platform: macos
|
||||||
|
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||||
|
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
||||||
|
|
||||||
# User provided section
|
# 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
|
sdk.dir=/Users/dwindown/Library/Android/sdk
|
||||||
flutter.sdk=/Users/dwindown/FlutterDev/flutter
|
flutter.sdk=/opt/homebrew/share/flutter
|
||||||
flutter.buildMode=release
|
flutter.buildMode=release
|
||||||
flutter.versionName=1.0.0
|
flutter.versionName=1.0.0
|
||||||
flutter.versionCode=1
|
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.
|
// This is a generated file; do not edit or check into version control.
|
||||||
FLUTTER_ROOT=/Users/dwindown/FlutterDev/flutter
|
FLUTTER_ROOT=/opt/homebrew/share/flutter
|
||||||
FLUTTER_APPLICATION_PATH=/Users/dwindown/CascadeProjects/jamshalat-diary
|
FLUTTER_APPLICATION_PATH=/Users/dwindown/Applications/jamshalat-diary
|
||||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||||
FLUTTER_TARGET=lib/main.dart
|
FLUTTER_TARGET=lib/main.dart
|
||||||
FLUTTER_BUILD_DIR=build
|
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
|
#!/bin/sh
|
||||||
# This is a generated file; do not edit or check into version control.
|
# This is a generated file; do not edit or check into version control.
|
||||||
export "FLUTTER_ROOT=/Users/dwindown/FlutterDev/flutter"
|
export "FLUTTER_ROOT=/opt/homebrew/share/flutter"
|
||||||
export "FLUTTER_APPLICATION_PATH=/Users/dwindown/CascadeProjects/jamshalat-diary"
|
export "FLUTTER_APPLICATION_PATH=/Users/dwindown/Applications/jamshalat-diary"
|
||||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||||
export "FLUTTER_TARGET=lib/main.dart"
|
export "FLUTTER_TARGET=lib/main.dart"
|
||||||
export "FLUTTER_BUILD_DIR=build"
|
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/laporan/presentation/laporan_screen.dart';
|
||||||
import '../features/tools/presentation/tools_screen.dart';
|
import '../features/tools/presentation/tools_screen.dart';
|
||||||
import '../features/dzikir/presentation/dzikir_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/qibla/presentation/qibla_screen.dart';
|
||||||
import '../features/quran/presentation/quran_screen.dart';
|
import '../features/quran/presentation/quran_screen.dart';
|
||||||
import '../features/quran/presentation/quran_reading_screen.dart';
|
import '../features/quran/presentation/quran_reading_screen.dart';
|
||||||
import '../features/quran/presentation/quran_murattal_screen.dart';
|
import '../features/quran/presentation/quran_murattal_screen.dart';
|
||||||
import '../features/quran/presentation/quran_bookmarks_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';
|
import '../features/settings/presentation/settings_screen.dart';
|
||||||
|
|
||||||
/// Navigation key for the shell navigator (bottom-nav screens).
|
/// Navigation key for the shell navigator (bottom-nav screens).
|
||||||
@@ -79,6 +82,11 @@ final GoRouter appRouter = GoRouter(
|
|||||||
parentNavigatorKey: _rootNavigatorKey,
|
parentNavigatorKey: _rootNavigatorKey,
|
||||||
builder: (context, state) => const QuranScreen(),
|
builder: (context, state) => const QuranScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'enrichment',
|
||||||
|
parentNavigatorKey: _rootNavigatorKey,
|
||||||
|
builder: (context, state) => const QuranEnrichmentScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'bookmarks',
|
path: 'bookmarks',
|
||||||
parentNavigatorKey: _rootNavigatorKey,
|
parentNavigatorKey: _rootNavigatorKey,
|
||||||
@@ -116,6 +124,16 @@ final GoRouter appRouter = GoRouter(
|
|||||||
parentNavigatorKey: _rootNavigatorKey,
|
parentNavigatorKey: _rootNavigatorKey,
|
||||||
builder: (context, state) => const QiblaScreen(),
|
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
|
// Simple Mode Tab: Zikir
|
||||||
@@ -128,6 +146,10 @@ final GoRouter appRouter = GoRouter(
|
|||||||
path: '/quran',
|
path: '/quran',
|
||||||
builder: (context, state) => const QuranScreen(isSimpleModeTab: true),
|
builder: (context, state) => const QuranScreen(isSimpleModeTab: true),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'enrichment',
|
||||||
|
builder: (context, state) => const QuranEnrichmentScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'bookmarks',
|
path: 'bookmarks',
|
||||||
builder: (context, state) => const QuranBookmarksScreen(),
|
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) ──
|
// ── Settings (pushed, no bottom nav) ──
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AppTextStyles {
|
|||||||
static const TextStyle arabicLarge = TextStyle(
|
static const TextStyle arabicLarge = TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w400,
|
||||||
height: 2.2,
|
height: 2.2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,18 @@ class AppSettings extends HiveObject {
|
|||||||
@HiveField(19)
|
@HiveField(19)
|
||||||
bool simpleMode; // false = Mode Lengkap, true = Mode Simpel
|
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({
|
AppSettings({
|
||||||
this.userName = 'User',
|
this.userName = 'User',
|
||||||
this.userEmail = '',
|
this.userEmail = '',
|
||||||
@@ -86,6 +98,10 @@ class AppSettings extends HiveObject {
|
|||||||
this.showLatin = true,
|
this.showLatin = true,
|
||||||
this.showTerjemahan = true,
|
this.showTerjemahan = true,
|
||||||
this.simpleMode = false,
|
this.simpleMode = false,
|
||||||
|
this.dzikirDisplayMode = 'list',
|
||||||
|
this.dzikirCounterButtonPosition = 'bottomPill',
|
||||||
|
this.dzikirAutoAdvance = true,
|
||||||
|
this.dzikirHapticOnCount = true,
|
||||||
}) : adhanEnabled = adhanEnabled ??
|
}) : adhanEnabled = adhanEnabled ??
|
||||||
{
|
{
|
||||||
'fajr': true,
|
'fajr': true,
|
||||||
|
|||||||
@@ -37,13 +37,17 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
|
|||||||
showLatin: fields.containsKey(17) ? fields[17] as bool? ?? true : true,
|
showLatin: fields.containsKey(17) ? fields[17] as bool? ?? true : true,
|
||||||
showTerjemahan: fields.containsKey(18) ? fields[18] as bool? ?? true : true,
|
showTerjemahan: fields.containsKey(18) ? fields[18] as bool? ?? true : true,
|
||||||
simpleMode: fields.containsKey(19) ? fields[19] as bool? ?? false : false,
|
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
|
@override
|
||||||
void write(BinaryWriter writer, AppSettings obj) {
|
void write(BinaryWriter writer, AppSettings obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(20)
|
..writeByte(24)
|
||||||
..writeByte(0)
|
..writeByte(0)
|
||||||
..write(obj.userName)
|
..write(obj.userName)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
@@ -83,7 +87,15 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
|
|||||||
..writeByte(18)
|
..writeByte(18)
|
||||||
..write(obj.showTerjemahan)
|
..write(obj.showTerjemahan)
|
||||||
..writeByte(19)
|
..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
|
@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/hive_boxes.dart';
|
||||||
import '../../../data/local/models/app_settings.dart';
|
import '../../../data/local/models/app_settings.dart';
|
||||||
import '../../../data/local/models/daily_worship_log.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';
|
import '../data/prayer_times_provider.dart';
|
||||||
|
|
||||||
class DashboardScreen extends ConsumerStatefulWidget {
|
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) {
|
Widget _buildAyatHariIni(BuildContext context, bool isDark) {
|
||||||
return FutureBuilder<Map<String, dynamic>?>(
|
return FutureBuilder<Map<String, dynamic>?>(
|
||||||
future: EQuranService.instance.getDailyAyat(),
|
future: MuslimApiService.instance.getDailyAyat(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -870,6 +914,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
height: 1.8,
|
height: 1.8,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.right,
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:lucide_icons/lucide_icons.dart';
|
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
|
||||||
import '../../../app/theme/app_colors.dart';
|
import '../../../app/theme/app_colors.dart';
|
||||||
import '../../../data/local/hive_boxes.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/app_settings.dart';
|
||||||
|
import '../../../data/local/models/dzikir_counter.dart';
|
||||||
|
import '../../../data/services/muslim_api_service.dart';
|
||||||
|
|
||||||
class DzikirScreen extends ConsumerStatefulWidget {
|
class DzikirScreen extends ConsumerStatefulWidget {
|
||||||
final bool isSimpleModeTab;
|
final bool isSimpleModeTab;
|
||||||
@@ -21,15 +22,36 @@ class DzikirScreen extends ConsumerStatefulWidget {
|
|||||||
class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late TabController _tabController;
|
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>> _pagiItems = [];
|
||||||
List<Map<String, dynamic>> _petangItems = [];
|
List<Map<String, dynamic>> _petangItems = [];
|
||||||
|
List<Map<String, dynamic>> _sesudahSholatItems = [];
|
||||||
|
bool _loading = true;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
late Box<DzikirCounter> _counterBox;
|
late Box<DzikirCounter> _counterBox;
|
||||||
late String _todayKey;
|
late String _todayKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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);
|
_counterBox = Hive.box<DzikirCounter>(HiveBoxes.dzikirCounters);
|
||||||
_todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now());
|
_todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now());
|
||||||
_loadData();
|
_loadData();
|
||||||
@@ -38,17 +60,68 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
|
for (final controller in _pageControllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadData() async {
|
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(() {
|
setState(() {
|
||||||
_pagiItems = List<Map<String, dynamic>>.from(json.decode(pagiJson));
|
_loading = true;
|
||||||
_petangItems = List<Map<String, dynamic>>.from(json.decode(petangJson));
|
_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';
|
final key = '${dzikirId}_$_todayKey';
|
||||||
var counter = _counterBox.get(key);
|
var counter = _counterBox.get(key);
|
||||||
|
final wasComplete = counter != null && counter.count >= counter.target;
|
||||||
|
|
||||||
if (counter == null) {
|
if (counter == null) {
|
||||||
counter = DzikirCounter(
|
counter = DzikirCounter(
|
||||||
dzikirId: dzikirId,
|
dzikirId: dzikirId,
|
||||||
@@ -74,40 +153,42 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
target: target,
|
target: target,
|
||||||
);
|
);
|
||||||
_counterBox.put(key, counter);
|
_counterBox.put(key, counter);
|
||||||
} else {
|
} else if (counter.count < counter.target) {
|
||||||
if (counter.count < counter.target) {
|
|
||||||
counter.count++;
|
counter.count++;
|
||||||
counter.save();
|
counter.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final isCompleteNow = counter.count >= counter.target;
|
||||||
|
if (hapticEnabled) {
|
||||||
|
HapticFeedback.lightImpact();
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
// Haptic feedback
|
return !wasComplete && isCompleteNow;
|
||||||
HapticFeedback.lightImpact();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final box = Hive.box<AppSettings>(HiveBoxes.settings);
|
|
||||||
final isSimpleMode = box.get('default')?.simpleMode ?? false;
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: !widget.isSimpleModeTab,
|
automaticallyImplyLeading: !widget.isSimpleModeTab,
|
||||||
title: const Text('Dzikir Pagi & Petang'),
|
title: const Text('Dzikir Harian'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {},
|
onPressed: _loadData,
|
||||||
icon: const Icon(LucideIcons.info),
|
icon: const Icon(LucideIcons.refreshCw),
|
||||||
|
tooltip: 'Muat ulang',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
bottom: TabBar(
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
// Tabs
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: TabBar(
|
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
labelColor: AppColors.primary,
|
labelColor: AppColors.primary,
|
||||||
unselectedLabelColor: isDark
|
unselectedLabelColor: isDark
|
||||||
@@ -116,47 +197,151 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
indicatorColor: AppColors.primary,
|
indicatorColor: AppColors.primary,
|
||||||
indicatorWeight: 3,
|
indicatorWeight: 3,
|
||||||
labelStyle:
|
labelStyle:
|
||||||
const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
|
||||||
tabs: const [
|
tabs: const [
|
||||||
Tab(text: 'Pagi'),
|
Tab(text: 'Pagi'),
|
||||||
Tab(text: 'Petang'),
|
Tab(text: 'Petang'),
|
||||||
|
Tab(text: 'Sesudah Sholat'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
body: _loading
|
||||||
child: TabBarView(
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _error != null
|
||||||
|
? _buildErrorState(isDark)
|
||||||
|
: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
_buildDzikirList(context, isDark, _pagiItems, 'pagi',
|
isFocusMode
|
||||||
'Dzikir Pagi', 'Dibaca setelah shalat Shubuh hingga terbit matahari'),
|
? _buildFocusModeTab(
|
||||||
_buildDzikirList(context, isDark, _petangItems, 'petang',
|
context,
|
||||||
'Dzikir Petang', 'Dibaca setelah shalat Ashar hingga terbenam matahari'),
|
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 _buildDzikirList(BuildContext context, bool isDark,
|
Widget _buildErrorState(bool isDark) {
|
||||||
List<Map<String, dynamic>> items, String prefix, String title, String subtitle) {
|
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,
|
||||||
|
AppSettings settings,
|
||||||
|
List<Map<String, dynamic>> items,
|
||||||
|
String prefix,
|
||||||
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
) {
|
||||||
if (items.isEmpty) {
|
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(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
itemCount: items.length + 1, // +1 for header
|
itemCount: items.length + 1,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 20),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(title,
|
Text(
|
||||||
|
title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 22, fontWeight: FontWeight.w800)),
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
@@ -174,8 +359,8 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final item = items[index - 1];
|
final item = items[index - 1];
|
||||||
final dzikirId = '${prefix}_${item['id']}';
|
final dzikirId = _resolveDzikirId(item, prefix, index - 1);
|
||||||
final target = (item['count'] as num?)?.toInt() ?? 1;
|
final target = (item['ulang'] as num?)?.toInt() ?? 1;
|
||||||
final counter = _getCounter(dzikirId, target);
|
final counter = _getCounter(dzikirId, target);
|
||||||
final isComplete = counter.count >= counter.target;
|
final isComplete = counter.count >= counter.target;
|
||||||
|
|
||||||
@@ -197,13 +382,14 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Header row: count badge + number
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 10, vertical: 4),
|
horizontal: 10,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primary.withValues(alpha: 0.12),
|
color: AppColors.primary.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
@@ -230,44 +416,37 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Arabic text
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Text(
|
child: Text(
|
||||||
item['arabic'] ?? '',
|
item['arab']?.toString() ?? '',
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
height: 2.0,
|
height: 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 10),
|
||||||
// Transliteration
|
|
||||||
Text(
|
Text(
|
||||||
item['transliteration'] ?? '',
|
'"${item['indo']?.toString() ?? ''}"',
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
color: AppColors.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
// Translation
|
|
||||||
Text(
|
|
||||||
'"${item['translation'] ?? ''}"',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: isDark
|
color: isDark
|
||||||
? AppColors.textSecondaryDark
|
? AppColors.textSecondaryDark
|
||||||
: AppColors.textSecondaryLight,
|
: AppColors.textSecondaryLight,
|
||||||
|
height: 1.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Counter button
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => _increment(dzikirId, target),
|
onTap: () => _increment(
|
||||||
|
dzikirId,
|
||||||
|
target,
|
||||||
|
hapticEnabled: settings.dzikirHapticOnCount,
|
||||||
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
@@ -281,7 +460,9 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isComplete ? LucideIcons.check : LucideIcons.fingerprint,
|
isComplete
|
||||||
|
? LucideIcons.check
|
||||||
|
: LucideIcons.fingerprint,
|
||||||
size: 18,
|
size: 18,
|
||||||
color: isComplete
|
color: isComplete
|
||||||
? AppColors.primary
|
? AppColors.primary
|
||||||
@@ -289,7 +470,7 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${counter.count} / $target',
|
isComplete ? 'Selesai' : '${counter.count} / $target',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.w700,
|
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 _showLatin = true;
|
||||||
bool _showTerjemahan = 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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -184,7 +192,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
|||||||
final dateStr = DateFormat('dd MMM yyyy, HH:mm').format(bookmark.savedAt);
|
final dateStr = DateFormat('dd MMM yyyy, HH:mm').format(bookmark.savedAt);
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => context.push('/tools/quran/${bookmark.surahId}?startVerse=${bookmark.verseId}'),
|
onTap: () => context.push(_readingRoute(bookmark.surahId, bookmark.verseId)),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -252,6 +260,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
height: 1.8,
|
height: 1.8,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -287,7 +296,8 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton.icon(
|
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),
|
icon: const Icon(LucideIcons.bookOpen, size: 18),
|
||||||
label: const Text('Lanjutkan Membaca'),
|
label: const Text('Lanjutkan Membaca'),
|
||||||
style: FilledButton.styleFrom(
|
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:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../../../app/theme/app_colors.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 '../../../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.
|
/// 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 {
|
class QuranMurattalScreen extends ConsumerStatefulWidget {
|
||||||
final String surahId;
|
final String surahId;
|
||||||
final String? initialQariId;
|
final String? initialQariId;
|
||||||
@@ -77,7 +74,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
|||||||
|
|
||||||
Future<void> _initDataAndPlayer() async {
|
Future<void> _initDataAndPlayer() async {
|
||||||
final surahNum = int.tryParse(widget.surahId) ?? 1;
|
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) {
|
if (data != null && mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -186,7 +183,10 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
|||||||
|
|
||||||
void _navigateToSurahNumber(int surahNum, {bool autoplay = false}) {
|
void _navigateToSurahNumber(int surahNum, {bool autoplay = false}) {
|
||||||
if (surahNum >= 1 && surahNum <= 114) {
|
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),
|
const SizedBox(height: 16),
|
||||||
...EQuranService.qariNames.entries.map((entry) {
|
...MuslimApiService.qariNames.entries.map((entry) {
|
||||||
final isSelected = entry.key == _selectedQariId;
|
final isSelected = entry.key == _selectedQariId;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
@@ -287,7 +287,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FutureBuilder<List<Map<String, dynamic>>>(
|
child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||||
future: EQuranService.instance.getAllSurahs(),
|
future: MuslimApiService.instance.getAllSurahs(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -339,7 +339,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (!isCurrentSurah) {
|
if (!isCurrentSurah) {
|
||||||
context.pushReplacement(
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
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 surahName = _surahData?['namaLatin'] ?? 'Surah ${widget.surahId}';
|
||||||
|
|
||||||
final hasPhoto = _unsplashPhoto != null;
|
final hasPhoto = _unsplashPhoto != null;
|
||||||
@@ -519,7 +517,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
|||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
// Qari name
|
// Qari name
|
||||||
Text(
|
Text(
|
||||||
EQuranService.qariNames[_selectedQariId] ?? 'Memuat...',
|
MuslimApiService.qariNames[_selectedQariId] ?? 'Memuat...',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -742,7 +740,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
|
|||||||
color: _unsplashPhoto != null ? Colors.white : AppColors.primary),
|
color: _unsplashPhoto != null ? Colors.white : AppColors.primary),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
EQuranService.qariNames[_selectedQariId] ?? 'Ganti Qari',
|
MuslimApiService.qariNames[_selectedQariId] ?? 'Ganti Qari',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
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/app_settings.dart';
|
||||||
import '../../../data/local/models/daily_worship_log.dart';
|
import '../../../data/local/models/daily_worship_log.dart';
|
||||||
import '../../../data/local/models/tilawah_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';
|
import '../../../core/providers/tilawah_tracking_provider.dart';
|
||||||
|
|
||||||
class QuranReadingScreen extends ConsumerStatefulWidget {
|
class QuranReadingScreen extends ConsumerStatefulWidget {
|
||||||
@@ -151,7 +151,8 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
|
|
||||||
Future<void> _loadSurah() async {
|
Future<void> _loadSurah() async {
|
||||||
final surahNum = int.tryParse(widget.surahId) ?? 1;
|
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) {
|
if (data != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_surah = data;
|
_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 endSurahId = _surah!['nomor'] ?? 1;
|
||||||
final endSurahName = _surah!['namaLatin'] ?? '';
|
final endSurahName = _surah!['namaLatin'] ?? '';
|
||||||
|
|
||||||
@@ -367,11 +370,14 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
calculatedAyat = (endVerseId - session.startVerseId).abs() + 1;
|
calculatedAyat = (endVerseId - session.startVerseId).abs() + 1;
|
||||||
} else {
|
} else {
|
||||||
// Cross surah calculation
|
// Cross surah calculation
|
||||||
final allSurahs = await EQuranService.instance.getAllSurahs();
|
final allSurahs = await MuslimApiService.instance.getAllSurahs();
|
||||||
if (allSurahs.isNotEmpty) {
|
if (allSurahs.isNotEmpty) {
|
||||||
int startSurahIdx = allSurahs.indexWhere((s) => s['nomor'] == session.startSurahId);
|
int startSurahIdx = allSurahs.indexWhere((s) => s['nomor'] == session.startSurahId);
|
||||||
int endSurahIdx = allSurahs.indexWhere((s) => s['nomor'] == endSurahId);
|
int endSurahIdx = allSurahs.indexWhere((s) => s['nomor'] == endSurahId);
|
||||||
|
|
||||||
|
if (startSurahIdx < 0 || endSurahIdx < 0) {
|
||||||
|
calculatedAyat = (endVerseId - session.startVerseId).abs() + 1;
|
||||||
|
} else {
|
||||||
// Ensure chronological calculation
|
// Ensure chronological calculation
|
||||||
if (startSurahIdx > endSurahIdx) {
|
if (startSurahIdx > endSurahIdx) {
|
||||||
final tempIdx = startSurahIdx; startSurahIdx = endSurahIdx; endSurahIdx = tempIdx;
|
final tempIdx = startSurahIdx; startSurahIdx = endSurahIdx; endSurahIdx = tempIdx;
|
||||||
@@ -387,6 +393,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
calculatedAyat += endVerseId; // Ayats inside EndSurah
|
calculatedAyat += endVerseId; // Ayats inside EndSurah
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
calculatedAyat = 1; // Fallback
|
calculatedAyat = 1; // Fallback
|
||||||
}
|
}
|
||||||
@@ -572,6 +579,11 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(LucideIcons.headphones),
|
||||||
|
tooltip: 'Murattal Surah',
|
||||||
|
onPressed: _navigateToMurattal,
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
LucideIcons.brain,
|
LucideIcons.brain,
|
||||||
@@ -620,6 +632,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -802,6 +815,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
height: 2.0,
|
height: 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import '../../../app/theme/app_colors.dart';
|
|||||||
import '../../../data/local/hive_boxes.dart';
|
import '../../../data/local/hive_boxes.dart';
|
||||||
import '../../../data/local/models/app_settings.dart';
|
import '../../../data/local/models/app_settings.dart';
|
||||||
import '../../../data/local/models/quran_bookmark.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 {
|
class QuranScreen extends ConsumerStatefulWidget {
|
||||||
final bool isSimpleModeTab;
|
final bool isSimpleModeTab;
|
||||||
@@ -36,7 +36,8 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadSurahs() async {
|
Future<void> _loadSurahs() async {
|
||||||
final data = await EQuranService.instance.getAllSurahs();
|
final data = await MuslimApiService.instance.getAllSurahs();
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_surahs = data;
|
_surahs = data;
|
||||||
_loading = false;
|
_loading = false;
|
||||||
@@ -100,8 +101,6 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
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
|
final filtered = _searchQuery.isEmpty
|
||||||
? _surahs
|
? _surahs
|
||||||
: _surahs
|
: _surahs
|
||||||
@@ -119,7 +118,15 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(LucideIcons.bookmark),
|
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(
|
IconButton(
|
||||||
icon: const Icon(LucideIcons.settings2),
|
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);
|
final hasLastRead = box.values.any((b) => b.isLastRead && b.surahId == number);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () =>
|
onTap: () => context.push(widget.isSimpleModeTab
|
||||||
context.push('/tools/quran/$number'),
|
? '/quran/$number'
|
||||||
|
: '/tools/quran/$number'),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 0, vertical: 6),
|
horizontal: 0, vertical: 6),
|
||||||
leading: Container(
|
leading: Container(
|
||||||
@@ -250,6 +258,7 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -244,6 +244,70 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
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 ──
|
// ── PRAYER SETTINGS ──
|
||||||
_sectionLabel('WAKTU SHOLAT'),
|
_sectionLabel('WAKTU SHOLAT'),
|
||||||
const SizedBox(height: 12),
|
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) {
|
void _showMethodDialog(BuildContext context) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:lucide_icons/lucide_icons.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
import '../../../app/theme/app_colors.dart';
|
import '../../../app/theme/app_colors.dart';
|
||||||
import '../../../core/widgets/tool_card.dart';
|
import '../../../core/widgets/tool_card.dart';
|
||||||
import '../../../data/services/equran_service.dart';
|
import '../../../data/services/muslim_api_service.dart';
|
||||||
|
|
||||||
class ToolsScreen extends ConsumerWidget {
|
class ToolsScreen extends ConsumerWidget {
|
||||||
const ToolsScreen({super.key});
|
const ToolsScreen({super.key});
|
||||||
@@ -29,7 +29,7 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -50,9 +50,9 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
child: ToolCard(
|
child: ToolCard(
|
||||||
icon: LucideIcons.bookOpen,
|
icon: LucideIcons.bookOpen,
|
||||||
title: 'Al-Quran\nTerjemahan',
|
title: 'Al-Quran\nTerjemahan',
|
||||||
color: const Color(0xFF00b894),
|
color: const Color(0xFF00B894),
|
||||||
isDark: isDark,
|
isDark: isDark,
|
||||||
onTap: () => context.push('/quran'),
|
onTap: () => context.push('/tools/quran'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -62,7 +62,7 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
title: 'Quran\nMurattal',
|
title: 'Quran\nMurattal',
|
||||||
color: const Color(0xFF7B61FF),
|
color: const Color(0xFF7B61FF),
|
||||||
isDark: isDark,
|
isDark: isDark,
|
||||||
onTap: () => context.push('/quran/1/murattal'),
|
onTap: () => context.push('/tools/quran/1/murattal'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -83,25 +83,65 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: ToolCard(
|
child: ToolCard(
|
||||||
icon: LucideIcons.sparkles,
|
icon: LucideIcons.sparkles,
|
||||||
title: 'Tasbih\nDigital',
|
title: 'Dzikir\nHarian',
|
||||||
color: AppColors.primary,
|
color: AppColors.primary,
|
||||||
isDark: isDark,
|
isDark: isDark,
|
||||||
onTap: () => context.push('/dzikir'),
|
onTap: () => context.push('/tools/dzikir'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 12),
|
||||||
// Ayat Hari Ini
|
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>?>(
|
FutureBuilder<Map<String, dynamic>?>(
|
||||||
future: EQuranService.instance.getDailyAyat(),
|
future: MuslimApiService.instance.getDailyAyat(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: const Center(child: CircularProgressIndicator()),
|
child: const Center(child: CircularProgressIndicator()),
|
||||||
@@ -109,7 +149,7 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!snapshot.hasData || snapshot.data == null) {
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
return const SizedBox.shrink(); // Hide if error/no internet
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = snapshot.data!;
|
final data = snapshot.data!;
|
||||||
@@ -117,7 +157,9 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -131,13 +173,19 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
color: isDark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondaryLight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(LucideIcons.share2,
|
icon: Icon(
|
||||||
|
LucideIcons.share2,
|
||||||
size: 18,
|
size: 18,
|
||||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight),
|
color: isDark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondaryLight,
|
||||||
|
),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -150,6 +198,7 @@ class ToolsScreen extends ConsumerWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Amiri',
|
fontFamily: 'Amiri',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
height: 1.8,
|
height: 1.8,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.right,
|
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|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_macos_build_settings(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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,16 +53,16 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
audio_service: cab6c1a0eaf01b5a35b567e11fa67d3cc1956910
|
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
|
||||||
audio_session: 728ae3823d914f809c485d390274861a24b0904e
|
audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e
|
||||||
flutter_local_notifications: 14e285ca39907db50704f7f46c9ab7a526bd7ead
|
flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd
|
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||||
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
|
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
|
||||||
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
url_launcher_macos: 175a54c831f4375a6cf895875f716ee5af3888ce
|
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 */
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile 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 */; };
|
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -62,11 +62,9 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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>"; };
|
0696C611C50F37ED234934AD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
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>"; };
|
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>"; };
|
||||||
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>"; };
|
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>"; };
|
||||||
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>"; };
|
|
||||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DDE68F59044EBC73D03E0962 /* Pods_RunnerTests.framework in Frameworks */,
|
E4654D13A28FD9D97A9BEAB5 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -103,27 +103,13 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0C90C3ED62E3E14394A23EE5 /* Pods_Runner.framework in Frameworks */,
|
41981E14C9DB7600396CF65B /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup 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 */ = {
|
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -150,8 +136,8 @@
|
|||||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||||
33CC10EE2044A3C60003C045 /* Products */,
|
33CC10EE2044A3C60003C045 /* Products */,
|
||||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
B1CD7E90020BCFEFA243FA5B /* Pods */,
|
||||||
0534C72D1883289C9A7D2A97 /* Pods */,
|
C5836D92F95B6223EA2AB79E /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -199,11 +185,25 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
B1CD7E90020BCFEFA243FA5B /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6A4B57B5283BA4F62AC20241 /* Pods_Runner.framework */,
|
A59D8BEEC1995674B843D406 /* Pods-Runner.debug.xcconfig */,
|
||||||
7DF3757EFF54A1EC85BA5E22 /* Pods_RunnerTests.framework */,
|
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;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
9B95A5421B9DABEF4DB6101B /* [CP] Check Pods Manifest.lock */,
|
CF2BEA357E30795FE78CD469 /* [CP] Check Pods Manifest.lock */,
|
||||||
331C80D1294CF70F00263BE5 /* Sources */,
|
331C80D1294CF70F00263BE5 /* Sources */,
|
||||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||||
331C80D3294CF70F00263BE5 /* Resources */,
|
331C80D3294CF70F00263BE5 /* Resources */,
|
||||||
@@ -234,13 +234,13 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
2DC954CD4D756DB6686B4570 /* [CP] Check Pods Manifest.lock */,
|
379A5F95EEFA0001FF3150C5 /* [CP] Check Pods Manifest.lock */,
|
||||||
33CC10E92044A3C60003C045 /* Sources */,
|
33CC10E92044A3C60003C045 /* Sources */,
|
||||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||||
33CC10EB2044A3C60003C045 /* Resources */,
|
33CC10EB2044A3C60003C045 /* Resources */,
|
||||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||||
7C73BC09BD737FBB6206AB8D /* [CP] Embed Pods Frameworks */,
|
080782848EF12E763C1C7A22 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -323,26 +323,21 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
2DC954CD4D756DB6686B4570 /* [CP] Check Pods Manifest.lock */ = {
|
080782848EF12E763C1C7A22 /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
name = "[CP] Embed Pods Frameworks";
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputFileListPaths = (
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||||
@@ -383,24 +378,29 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
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;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputFileListPaths = (
|
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 = (
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
9B95A5421B9DABEF4DB6101B /* [CP] Check Pods Manifest.lock */ = {
|
CF2BEA357E30795FE78CD469 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 1F212DF96DD0BB5851DDFC62 /* Pods-RunnerTests.debug.xcconfig */;
|
baseConfigurationReference = CEFABEB8B68EDF9F2E4321B2 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -488,7 +488,7 @@
|
|||||||
};
|
};
|
||||||
331C80DC294CF71000263BE5 /* Release */ = {
|
331C80DC294CF71000263BE5 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 06DAA92E91CF7851957A0E28 /* Pods-RunnerTests.release.xcconfig */;
|
baseConfigurationReference = 0EA81656FD366AC44330F725 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -503,7 +503,7 @@
|
|||||||
};
|
};
|
||||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = E6FD2B4E523D8881848DBBE0 /* Pods-RunnerTests.profile.xcconfig */;
|
baseConfigurationReference = 4AA0251CD9D710A50D527654 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
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 |