Compare commits
2 Commits
2d09b5b356
...
c32b56c00e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c32b56c00e | ||
|
|
e34dd6cf06 |
8
PRD.md
@@ -1,5 +1,5 @@
|
|||||||
# Product Requirements Document
|
# Product Requirements Document
|
||||||
## Jamshalat Diary — Islamic Worship Companion App
|
## JamShalat — Islamic Worship Companion App
|
||||||
|
|
||||||
**Version:** 1.0
|
**Version:** 1.0
|
||||||
**Date:** March 2026
|
**Date:** March 2026
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
## 1. Overview
|
## 1. Overview
|
||||||
|
|
||||||
**Jamshalat Diary** is an offline-first Muslim daily worship companion app built in Flutter. It helps Muslim users track their daily prayers, worship habits, and spiritual growth with a clean, modern UI that supports both light and dark themes.
|
**JamShalat** is an offline-first Muslim daily worship companion app built in Flutter. It helps Muslim users track their daily prayers, worship habits, and spiritual growth with a clean, modern UI that supports both light and dark themes.
|
||||||
|
|
||||||
**Core value proposition:**
|
**Core value proposition:**
|
||||||
- Never miss a prayer — real-time prayer time countdowns with Adhan/Iqamah notifications
|
- Never miss a prayer — real-time prayer time countdowns with Adhan/Iqamah notifications
|
||||||
@@ -683,7 +683,7 @@ Rows:
|
|||||||
Label: "ABOUT"
|
Label: "ABOUT"
|
||||||
|
|
||||||
Rows:
|
Rows:
|
||||||
- App Version: "Jamshalat Diary v1.0.0"
|
- App Version: "JamShalat v1.0.0"
|
||||||
- Privacy Policy (launches in-app browser)
|
- Privacy Policy (launches in-app browser)
|
||||||
- Rate the App (links to store)
|
- Rate the App (links to store)
|
||||||
- Contact / Feedback
|
- Contact / Feedback
|
||||||
@@ -881,4 +881,4 @@ The following features are **explicitly excluded** from v1.0 to keep scope focus
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*PRD v1.0 — Jamshalat Diary — March 2026*
|
*PRD v1.0 — JamShalat — March 2026*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# jamshalat_diary
|
# JamShalat
|
||||||
|
|
||||||
A new Flutter project.
|
A new Flutter project.
|
||||||
|
|
||||||
|
|||||||
10
TASKLIST.md
@@ -1,6 +1,6 @@
|
|||||||
# Jamshalat Diary — Phase-Based Implementation Tasklist
|
# JamShalat — Phase-Based Implementation Tasklist
|
||||||
|
|
||||||
**Project:** Jamshalat Diary Flutter App
|
**Project:** JamShalat Flutter App
|
||||||
**PRD Reference:** `PRD.md`
|
**PRD Reference:** `PRD.md`
|
||||||
**Design Reference:** `stitch/` folder (18 screens)
|
**Design Reference:** `stitch/` folder (18 screens)
|
||||||
**Last Updated:** March 2026
|
**Last Updated:** March 2026
|
||||||
@@ -302,8 +302,8 @@
|
|||||||
- [ ] **7.3.1** Create app icon (1024×1024px): mosque/compass motif with `#70df20` primary color
|
- [ ] **7.3.1** Create app icon (1024×1024px): mosque/compass motif with `#70df20` primary color
|
||||||
- [ ] **7.3.2** Apply app icon via `flutter_launcher_icons` package (all densities, adaptive icon for Android)
|
- [ ] **7.3.2** Apply app icon via `flutter_launcher_icons` package (all densities, adaptive icon for Android)
|
||||||
- [ ] **7.3.3** Create splash screen via `flutter_native_splash` package (white/dark bg, centered logo)
|
- [ ] **7.3.3** Create splash screen via `flutter_native_splash` package (white/dark bg, centered logo)
|
||||||
- [ ] **7.3.4** Set app name: "Jamshalat Diary" in `AndroidManifest.xml` and `Info.plist`
|
- [ ] **7.3.4** Set app name: "JamShalat" in `AndroidManifest.xml` and `Info.plist`
|
||||||
- [ ] **7.3.5** Set bundle ID: `com.jamshalat.diary` on both platforms
|
- [ ] **7.3.5** Set bundle ID: `com.jamshalat.app` on both platforms
|
||||||
- [ ] **7.3.6** Configure release signing (Android keystore, iOS certificates) — document in private README
|
- [ ] **7.3.6** Configure release signing (Android keystore, iOS certificates) — document in private README
|
||||||
- [ ] **7.3.7** Build release APK: `flutter build apk --release` — verify no build errors
|
- [ ] **7.3.7** Build release APK: `flutter build apk --release` — verify no build errors
|
||||||
- [ ] **7.3.8** Build iOS release: `flutter build ipa --release` — verify no build errors
|
- [ ] **7.3.8** Build iOS release: `flutter build ipa --release` — verify no build errors
|
||||||
@@ -338,4 +338,4 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*TASKLIST v1.0 — Jamshalat Diary — March 2026*
|
*TASKLIST v1.0 — JamShalat — March 2026*
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ val hasReleaseKeystore = listOf(
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.jamshalat.diary"
|
namespace = "com.jamshalat.app"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "com.jamshalat.diary"
|
applicationId = "com.jamshalat.app"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||||
<application
|
<application
|
||||||
android:label="Jamshalat Diary"
|
android:label="JamShalat"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
101
android/app/src/main/kotlin/com/jamshalat/app/MainActivity.kt
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package com.jamshalat.app
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.hardware.GeomagneticField
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import com.ryanheise.audioservice.AudioServiceActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodCall
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MainActivity : AudioServiceActivity() {
|
||||||
|
companion object {
|
||||||
|
private const val GEOMAGNETIC_CHANNEL = "com.jamshalat.app/geomagnetic"
|
||||||
|
private const val SHARE_CHANNEL = "com.jamshalat.app/share"
|
||||||
|
private const val DECLINATION_METHOD = "getDeclination"
|
||||||
|
private const val SHARE_IMAGE_METHOD = "shareImage"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, GEOMAGNETIC_CHANNEL)
|
||||||
|
.setMethodCallHandler(::handleGeomagneticMethodCall)
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SHARE_CHANNEL)
|
||||||
|
.setMethodCallHandler(::handleShareMethodCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleGeomagneticMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
if (call.method != DECLINATION_METHOD) {
|
||||||
|
result.notImplemented()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val latitude = call.argument<Double>("latitude")
|
||||||
|
val longitude = call.argument<Double>("longitude")
|
||||||
|
if (latitude == null || longitude == null) {
|
||||||
|
result.error("INVALID_ARGS", "Latitude and longitude are required.", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val altitude = call.argument<Double>("altitude") ?: 0.0
|
||||||
|
val timestamp = call.argument<Long>("timestamp") ?: System.currentTimeMillis()
|
||||||
|
val field = GeomagneticField(
|
||||||
|
latitude.toFloat(),
|
||||||
|
longitude.toFloat(),
|
||||||
|
altitude.toFloat(),
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
result.success(field.declination.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleShareMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
if (call.method != SHARE_IMAGE_METHOD) {
|
||||||
|
result.notImplemented()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = call.argument<String>("path")
|
||||||
|
if (path.isNullOrBlank()) {
|
||||||
|
result.error("INVALID_ARGS", "Path is required.", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(path)
|
||||||
|
if (!file.exists()) {
|
||||||
|
result.error("FILE_NOT_FOUND", "Shared image file does not exist.", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val mimeType = call.argument<String>("mimeType") ?: "image/png"
|
||||||
|
val chooserTitle = call.argument<String>("chooserTitle")
|
||||||
|
val authority = "${applicationContext.packageName}.flutter.share_provider"
|
||||||
|
val fileUri = FileProvider.getUriForFile(this, authority, file)
|
||||||
|
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
type = mimeType
|
||||||
|
putExtra(Intent.EXTRA_STREAM, fileUri)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
val chooserIntent = Intent.createChooser(shareIntent, chooserTitle)
|
||||||
|
|
||||||
|
packageManager.queryIntentActivities(
|
||||||
|
chooserIntent,
|
||||||
|
PackageManager.MATCH_DEFAULT_ONLY,
|
||||||
|
).forEach { resolveInfo ->
|
||||||
|
grantUriPermission(
|
||||||
|
resolveInfo.activityInfo.packageName,
|
||||||
|
fileUri,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(chooserIntent)
|
||||||
|
result.success(null)
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
result.error("SHARE_FAILED", error.message, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package com.jamshalat.diary
|
|
||||||
|
|
||||||
import android.hardware.GeomagneticField
|
|
||||||
import com.ryanheise.audioservice.AudioServiceActivity
|
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
|
|
||||||
class MainActivity : AudioServiceActivity() {
|
|
||||||
companion object {
|
|
||||||
private const val GEOMAGNETIC_CHANNEL = "com.jamshalat.diary/geomagnetic"
|
|
||||||
private const val DECLINATION_METHOD = "getDeclination"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
|
||||||
super.configureFlutterEngine(flutterEngine)
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, GEOMAGNETIC_CHANNEL)
|
|
||||||
.setMethodCallHandler(::handleGeomagneticMethodCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleGeomagneticMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
|
||||||
if (call.method != DECLINATION_METHOD) {
|
|
||||||
result.notImplemented()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val latitude = call.argument<Double>("latitude")
|
|
||||||
val longitude = call.argument<Double>("longitude")
|
|
||||||
if (latitude == null || longitude == null) {
|
|
||||||
result.error("INVALID_ARGS", "Latitude and longitude are required.", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val altitude = call.argument<Double>("altitude") ?: 0.0
|
|
||||||
val timestamp = call.argument<Long>("timestamp") ?: System.currentTimeMillis()
|
|
||||||
val field = GeomagneticField(
|
|
||||||
latitude.toFloat(),
|
|
||||||
longitude.toFloat(),
|
|
||||||
altitude.toFloat(),
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
result.success(field.declination.toDouble())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.jamshalat.jamshalat_diary
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
|
||||||
BIN
android/app/upload-keystore.p12.bak
Normal file
412
docs/app-feature-overview.md
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
# JamShalat: App Feature Overview
|
||||||
|
|
||||||
|
## What this app does
|
||||||
|
|
||||||
|
JamShalat is a Muslim daily worship companion. It combines prayer schedule utilities, Qur'an reading, dzikir, Islamic reference content, worship tracking, reminders, and personal settings in one app.
|
||||||
|
|
||||||
|
The app is designed to help users:
|
||||||
|
|
||||||
|
- know the current and upcoming prayer times
|
||||||
|
- keep worship routines more consistent
|
||||||
|
- continue Qur'an reading from the last ayat
|
||||||
|
- access daily dzikir, doa, hadits, and Qur'an study tools
|
||||||
|
- receive adzan, iqamah, and habit reminders
|
||||||
|
- review weekly, monthly, and yearly worship progress
|
||||||
|
|
||||||
|
## Main navigation modes
|
||||||
|
|
||||||
|
The app supports 2 navigation modes:
|
||||||
|
|
||||||
|
### 1. Mode lengkap
|
||||||
|
|
||||||
|
Bottom navigation:
|
||||||
|
|
||||||
|
- Beranda
|
||||||
|
- Jadwal
|
||||||
|
- Checklist
|
||||||
|
- Laporan
|
||||||
|
- Alat Islami
|
||||||
|
|
||||||
|
This mode is focused on full habit tracking and reporting.
|
||||||
|
|
||||||
|
### 2. Mode simpel
|
||||||
|
|
||||||
|
Bottom navigation:
|
||||||
|
|
||||||
|
- Beranda
|
||||||
|
- Jadwal
|
||||||
|
- Al-Qur'an
|
||||||
|
- Dzikir
|
||||||
|
- Lainnya
|
||||||
|
|
||||||
|
This mode is focused on faster access to the most-used spiritual features with less UI complexity.
|
||||||
|
|
||||||
|
## Core screens and features
|
||||||
|
|
||||||
|
## Beranda
|
||||||
|
|
||||||
|
Beranda is the main daily dashboard.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- main hero card with the next prayer time
|
||||||
|
- live countdown to the next prayer
|
||||||
|
- city/location label
|
||||||
|
- quick access button to Arah Kiblat
|
||||||
|
- sound toggle for adzan reminders
|
||||||
|
- horizontal prayer schedule cards for today or tomorrow
|
||||||
|
- automatic highlight for the next relevant prayer card
|
||||||
|
- `Lanjutkan Tilawah` card when the user has a last-read Qur'an bookmark
|
||||||
|
- `Poin Ibadah Hari Ini` summary card
|
||||||
|
- weekly points progress chart
|
||||||
|
- `Ayat Hari Ini` card
|
||||||
|
|
||||||
|
`Ayat Hari Ini` supports:
|
||||||
|
|
||||||
|
- deterministic daily ayat
|
||||||
|
- randomize to another ayat
|
||||||
|
- restore back to the daily ayat
|
||||||
|
- share as text
|
||||||
|
- copy text
|
||||||
|
- share as generated image card
|
||||||
|
|
||||||
|
## Kalender Sholat / Jadwal
|
||||||
|
|
||||||
|
This screen is the prayer calendar and city-based schedule browser.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- current city prayer schedule
|
||||||
|
- date-aware prayer timing
|
||||||
|
- support for changing city/kabupaten
|
||||||
|
- schedule browsing for upcoming days
|
||||||
|
- layout optimized for simple mode and full mode
|
||||||
|
|
||||||
|
## Checklist Ibadah
|
||||||
|
|
||||||
|
This feature is available in mode lengkap.
|
||||||
|
|
||||||
|
It acts as the daily worship tracker.
|
||||||
|
|
||||||
|
Tracked areas:
|
||||||
|
|
||||||
|
- 5 daily sholat fardhu
|
||||||
|
- rawatib prayers
|
||||||
|
- tilawah target progress
|
||||||
|
- dzikir pagi and petang
|
||||||
|
- puasa sunnah
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- daily completion progress
|
||||||
|
- configurable rawatib level
|
||||||
|
- configurable tilawah target
|
||||||
|
- optional additional worship tracking
|
||||||
|
- automatic recalculation of total points and completion percentage
|
||||||
|
- integration with tilawah and dzikir flows
|
||||||
|
|
||||||
|
## Laporan / Riwayat Ibadah
|
||||||
|
|
||||||
|
This screen summarizes worship quality and history.
|
||||||
|
|
||||||
|
Mode lengkap:
|
||||||
|
|
||||||
|
- weekly tab
|
||||||
|
- monthly tab
|
||||||
|
- yearly tab
|
||||||
|
- weekly average summary
|
||||||
|
- daily point bars
|
||||||
|
- per-day point labels
|
||||||
|
- insights for strongest and weakest worship consistency
|
||||||
|
|
||||||
|
Mode simpel:
|
||||||
|
|
||||||
|
- simplified worship history presentation
|
||||||
|
|
||||||
|
## Al-Qur'an
|
||||||
|
|
||||||
|
This module contains the Qur'an reading experience.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- list of all surah
|
||||||
|
- surah metadata
|
||||||
|
- configurable display:
|
||||||
|
- Arabic only
|
||||||
|
- Arabic + Latin
|
||||||
|
- Arabic + translation
|
||||||
|
- adaptive Arabic font size support
|
||||||
|
- direct access to bookmarks
|
||||||
|
- direct access to enrichment tools
|
||||||
|
|
||||||
|
## Qur'an reading screen
|
||||||
|
|
||||||
|
This is the detailed surah reading screen.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- verse-by-verse reading
|
||||||
|
- Bismillah shown as part of scroll flow instead of fixed header
|
||||||
|
- accurate jump to a target ayat
|
||||||
|
- jump support for:
|
||||||
|
- last read
|
||||||
|
- favorites
|
||||||
|
- continue reading
|
||||||
|
- per-ayat actions:
|
||||||
|
- play verse audio
|
||||||
|
- mark as last read
|
||||||
|
- add to favorites
|
||||||
|
- open tafsir drawer
|
||||||
|
- display settings drawer
|
||||||
|
- quick Arabic font size adjustment
|
||||||
|
- optional Latin and translation display
|
||||||
|
- tilawah session tracking
|
||||||
|
- sync to checklist in mode lengkap
|
||||||
|
|
||||||
|
## Qur'an bookmarks
|
||||||
|
|
||||||
|
This screen stores reading continuity and personal saved ayat.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- `Terakhir Dibaca`
|
||||||
|
- `Ayat Favorit`
|
||||||
|
- open exact surah and ayat
|
||||||
|
- cached ayat content refresh when needed
|
||||||
|
- continue reading CTA
|
||||||
|
|
||||||
|
## Qur'an Murattal
|
||||||
|
|
||||||
|
This is the full-surah audio recitation player.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- play murattal by surah
|
||||||
|
- qari selection
|
||||||
|
- autoplay support
|
||||||
|
- previous/next/shuffle controls
|
||||||
|
- gold-decorated main play button
|
||||||
|
- blurred themed player panel
|
||||||
|
- background artwork
|
||||||
|
- shortcut back to the same surah in reading mode
|
||||||
|
- background playback through shared app-wide audio player
|
||||||
|
|
||||||
|
## Qur'an Enrichment
|
||||||
|
|
||||||
|
This screen provides deeper study tools.
|
||||||
|
|
||||||
|
Tabs:
|
||||||
|
|
||||||
|
- Cari
|
||||||
|
- Tafsir
|
||||||
|
- Juz
|
||||||
|
- Halaman
|
||||||
|
- Tema
|
||||||
|
- Asmaul Husna
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- search ayat
|
||||||
|
- tafsir per surah
|
||||||
|
- word-by-word expansion
|
||||||
|
- browse by juz
|
||||||
|
- browse by page
|
||||||
|
- browse by topic/theme
|
||||||
|
- Asmaul Husna reference
|
||||||
|
- Arabic font-size settings in screen
|
||||||
|
|
||||||
|
## Dzikir Harian
|
||||||
|
|
||||||
|
This screen organizes daily dzikir routines.
|
||||||
|
|
||||||
|
Tabs:
|
||||||
|
|
||||||
|
- Sesudah Sholat
|
||||||
|
- Pagi
|
||||||
|
- Petang
|
||||||
|
- Harian
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- card mode and focus/slide mode
|
||||||
|
- pembuka slide support for pagi and petang
|
||||||
|
- per-item recitation counter
|
||||||
|
- source structure aligned to Rumaysho reference
|
||||||
|
- daily dzikir separated from morning/evening flow where appropriate
|
||||||
|
- automatic sync between linked dzikir counters
|
||||||
|
- prayer-window reset for `Sesudah Sholat`
|
||||||
|
- day-based reset for `Pagi`, `Petang`, and `Harian`
|
||||||
|
- configurable Arabic font size in-screen
|
||||||
|
- dedicated simple-mode behavior
|
||||||
|
|
||||||
|
## Arah Kiblat
|
||||||
|
|
||||||
|
This is the compass-based qibla finder.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- live compass tracking
|
||||||
|
- location-based qibla direction calculation
|
||||||
|
- device support checks
|
||||||
|
- permission/state handling
|
||||||
|
- themed background with subtle Unsplash imagery
|
||||||
|
- Kaaba-centered compass UI
|
||||||
|
- visual indication when the device is aligned correctly to the qibla direction
|
||||||
|
|
||||||
|
## Kumpulan Doa
|
||||||
|
|
||||||
|
Reference screen for Islamic prayers.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- searchable list of doa
|
||||||
|
- Arabic text
|
||||||
|
- translation and supporting text
|
||||||
|
- refresh action
|
||||||
|
- Arabic font size setting in-screen
|
||||||
|
|
||||||
|
## Hadits Arba'in
|
||||||
|
|
||||||
|
Reference screen for the 40 hadith collection.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- searchable hadith list
|
||||||
|
- Arabic text
|
||||||
|
- translation/explanation text
|
||||||
|
- refresh action
|
||||||
|
- Arabic font size setting in-screen
|
||||||
|
|
||||||
|
## Alat Islami / Lainnya
|
||||||
|
|
||||||
|
This acts as the tool hub.
|
||||||
|
|
||||||
|
Contains shortcuts to:
|
||||||
|
|
||||||
|
- Al-Qur'an Terjemahan
|
||||||
|
- Qur'an Murattal
|
||||||
|
- Arah Kiblat
|
||||||
|
- Dzikir Harian
|
||||||
|
- Kumpulan Doa
|
||||||
|
- Hadits Arba'in
|
||||||
|
- Pendalaman Al-Qur'an
|
||||||
|
|
||||||
|
It also contains:
|
||||||
|
|
||||||
|
- `Ayat Hari Ini`
|
||||||
|
- share actions for the ayat
|
||||||
|
- randomize another ayat
|
||||||
|
|
||||||
|
## Notifications
|
||||||
|
|
||||||
|
The app has a dedicated notification center.
|
||||||
|
|
||||||
|
Tabs:
|
||||||
|
|
||||||
|
- Alarm
|
||||||
|
- Pesan
|
||||||
|
|
||||||
|
Alarm tab features:
|
||||||
|
|
||||||
|
- list of scheduled adzan notifications
|
||||||
|
- list of scheduled iqamah reminders
|
||||||
|
- `Akan Datang` filter
|
||||||
|
- `Sudah Lewat` filter
|
||||||
|
- refresh action
|
||||||
|
|
||||||
|
Pesan tab features:
|
||||||
|
|
||||||
|
- inbox-style message list
|
||||||
|
- read/unread state
|
||||||
|
- mark all as read
|
||||||
|
- system and non-prayer message support
|
||||||
|
|
||||||
|
## Adzan and reminders
|
||||||
|
|
||||||
|
The app schedules local notifications based on the selected city and prayer times.
|
||||||
|
|
||||||
|
Supported reminders:
|
||||||
|
|
||||||
|
- adzan
|
||||||
|
- iqamah
|
||||||
|
- checklist reminder
|
||||||
|
- other non-prayer habit/system reminders
|
||||||
|
|
||||||
|
Implementation highlights:
|
||||||
|
|
||||||
|
- local timezone-aware scheduling
|
||||||
|
- exact alarm support on Android where available
|
||||||
|
- permission handling for notification and exact alarm access
|
||||||
|
- per-prayer enable/disable behavior
|
||||||
|
|
||||||
|
## Settings and personalization
|
||||||
|
|
||||||
|
The app includes a broad settings screen.
|
||||||
|
|
||||||
|
Sections include:
|
||||||
|
|
||||||
|
- Preferensi
|
||||||
|
- Pemberitahuan
|
||||||
|
- Checklist Ibadah
|
||||||
|
- Tampilan Dzikir
|
||||||
|
- Waktu Sholat
|
||||||
|
- Tampilan
|
||||||
|
- Tentang
|
||||||
|
|
||||||
|
Key settings:
|
||||||
|
|
||||||
|
- mode lengkap / mode simpel
|
||||||
|
- light mode / dark mode
|
||||||
|
- city selection
|
||||||
|
- prayer calculation method
|
||||||
|
- iqamah offset per prayer
|
||||||
|
- adzan notification toggle
|
||||||
|
- global alert toggle
|
||||||
|
- inbox/message toggle
|
||||||
|
- quiet hours
|
||||||
|
- maximum non-prayer pushes per day
|
||||||
|
- checklist reminder time
|
||||||
|
- rawatib level
|
||||||
|
- tilawah target
|
||||||
|
- dzikir and puasa tracking toggles
|
||||||
|
- Arabic font size
|
||||||
|
- profile editing
|
||||||
|
- data reset
|
||||||
|
|
||||||
|
## Visual and UX features
|
||||||
|
|
||||||
|
The app also includes a number of presentation-focused improvements:
|
||||||
|
|
||||||
|
- custom bottom navigation with premium gold active state
|
||||||
|
- hidden theme toggle revealed by sliding the bottom bar
|
||||||
|
- HugeIcons-based icon system
|
||||||
|
- dark and light mode support
|
||||||
|
- adaptive Arabic typography
|
||||||
|
- reusable share card generation for ayat
|
||||||
|
- route-aware bottom bar visibility in focus-heavy screens
|
||||||
|
|
||||||
|
## Data and persistence
|
||||||
|
|
||||||
|
The app stores user state locally for continuity.
|
||||||
|
|
||||||
|
Persisted examples:
|
||||||
|
|
||||||
|
- settings
|
||||||
|
- worship logs
|
||||||
|
- dzikir progress
|
||||||
|
- tilawah progress
|
||||||
|
- Qur'an last read
|
||||||
|
- Qur'an favorites
|
||||||
|
- notification inbox state
|
||||||
|
|
||||||
|
## In short
|
||||||
|
|
||||||
|
JamShalat is not only a prayer-time app. It is a daily Islamic companion that combines:
|
||||||
|
|
||||||
|
- prayer schedule
|
||||||
|
- adzan and iqamah reminders
|
||||||
|
- qibla finder
|
||||||
|
- Qur'an reading and murattal
|
||||||
|
- dzikir, doa, and hadith references
|
||||||
|
- worship checklist and reporting
|
||||||
|
- personal spiritual continuity through bookmarks, reminders, and progress tracking
|
||||||
222
docs/google-play-form-guide-id.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
# Panduan Isi Form Google Play Console
|
||||||
|
|
||||||
|
Dokumen ini berisi isi yang disarankan untuk form Play Console dalam Bahasa Indonesia. Isi ini disusun dari kondisi project saat ini.
|
||||||
|
|
||||||
|
## Status yang sudah ada di repo
|
||||||
|
|
||||||
|
- [x] Nama aplikasi sudah ditetapkan: `JamShalat`
|
||||||
|
- [x] Branding aplikasi sudah konsisten di Android, iOS, macOS, dan web
|
||||||
|
- [x] Package name Android sudah diset ke `com.jamshalat.app`
|
||||||
|
- [x] Aplikasi sudah punya keystore rilis
|
||||||
|
- [x] Aplikasi sudah punya ikon custom
|
||||||
|
|
||||||
|
## Identitas aplikasi
|
||||||
|
|
||||||
|
### Nama aplikasi
|
||||||
|
|
||||||
|
`JamShalat`
|
||||||
|
|
||||||
|
### Kategori yang disarankan
|
||||||
|
|
||||||
|
`Gaya Hidup`
|
||||||
|
|
||||||
|
Alasan:
|
||||||
|
- aplikasi ini dipakai untuk ibadah harian
|
||||||
|
- fitur utamanya adalah shalat, Qur'an, dzikir, doa, qibla, dan laporan progres
|
||||||
|
|
||||||
|
### Tag atau kata kunci yang disarankan
|
||||||
|
|
||||||
|
- shalat
|
||||||
|
- jadwal shalat
|
||||||
|
- qibla
|
||||||
|
- kiblat
|
||||||
|
- al-quran
|
||||||
|
- qur'an
|
||||||
|
- dzikir
|
||||||
|
- doa
|
||||||
|
- hadits
|
||||||
|
- murattal
|
||||||
|
- ibadah
|
||||||
|
- muslim
|
||||||
|
- ramadan
|
||||||
|
- tafsir
|
||||||
|
|
||||||
|
## Deskripsi singkat
|
||||||
|
|
||||||
|
Gunakan salah satu versi ini.
|
||||||
|
|
||||||
|
### Opsi 1
|
||||||
|
|
||||||
|
Teman ibadah harian untuk shalat, Qur'an, dzikir, doa, dan laporan progres.
|
||||||
|
|
||||||
|
### Opsi 2
|
||||||
|
|
||||||
|
Pantau shalat, baca Qur'an, dan jaga rutinitas ibadah dalam satu aplikasi.
|
||||||
|
|
||||||
|
## Deskripsi lengkap
|
||||||
|
|
||||||
|
Berikut draft deskripsi lengkap yang bisa langsung dipakai.
|
||||||
|
|
||||||
|
JamShalat adalah aplikasi pendamping ibadah harian untuk membantu pengguna menjaga rutinitas shalat, membaca Al-Qur'an, berdzikir, berdoa, dan memantau progres ibadah dengan lebih rapi.
|
||||||
|
|
||||||
|
Fitur utama:
|
||||||
|
|
||||||
|
- jadwal shalat harian dan kalender shalat
|
||||||
|
- hitung mundur menuju shalat berikutnya
|
||||||
|
- arah kiblat
|
||||||
|
- notifikasi adzan, iqamah, dan pengingat ibadah
|
||||||
|
- baca Al-Qur'an per surah dan per ayat
|
||||||
|
- lanjutkan bacaan dari ayat terakhir
|
||||||
|
- favorit ayat dan marka bacaan
|
||||||
|
- murattal per surah dengan pemutar audio
|
||||||
|
- tafsir, juz, halaman, tema, dan Asmaul Husna
|
||||||
|
- dzikir pagi, petang, dan sesudah shalat
|
||||||
|
- target dzikir harian yang bisa dicicil sepanjang hari
|
||||||
|
- daftar doa dan hadits
|
||||||
|
- checklist ibadah harian
|
||||||
|
- laporan progres ibadah mingguan, bulanan, dan tahunan
|
||||||
|
- ayat hari ini yang bisa dibagikan sebagai teks atau gambar
|
||||||
|
|
||||||
|
JamShalat dibuat agar:
|
||||||
|
|
||||||
|
- ringan dipakai setiap hari
|
||||||
|
- mudah dipakai dalam mode simpel maupun mode lengkap
|
||||||
|
- membantu pengguna fokus ke ibadah tanpa UI yang berlebihan
|
||||||
|
- menyimpan progres secara lokal di perangkat
|
||||||
|
|
||||||
|
Catatan:
|
||||||
|
|
||||||
|
- aplikasi ini dapat memakai lokasi perangkat untuk arah kiblat dan jadwal shalat
|
||||||
|
- aplikasi ini dapat memakai notifikasi untuk pengingat ibadah
|
||||||
|
- sebagian konten juga dapat diambil dari layanan online agar data lebih lengkap
|
||||||
|
|
||||||
|
## Ikon aplikasi
|
||||||
|
|
||||||
|
Gunakan ikon app yang konsisten dengan branding `JamShalat`.
|
||||||
|
|
||||||
|
Status di repo:
|
||||||
|
|
||||||
|
- [x] ikon custom sudah tersedia
|
||||||
|
- [ ] pastikan versi store-ready tanpa elemen yang terlalu kecil atau terlalu ramai
|
||||||
|
|
||||||
|
## Screenshot Play Store
|
||||||
|
|
||||||
|
Siapkan minimal beberapa screenshot yang menunjukkan:
|
||||||
|
|
||||||
|
- Beranda mode simpel
|
||||||
|
- Beranda mode lengkap
|
||||||
|
- Al-Qur'an
|
||||||
|
- Dzikir
|
||||||
|
- Arah Kiblat
|
||||||
|
- Laporan
|
||||||
|
|
||||||
|
Saran:
|
||||||
|
|
||||||
|
- pakai screenshot yang bersih dan terang
|
||||||
|
- jangan terlalu banyak teks kecil
|
||||||
|
- tampilkan UI yang paling menarik
|
||||||
|
|
||||||
|
## Feature graphic
|
||||||
|
|
||||||
|
Siapkan banner feature graphic untuk Play Store.
|
||||||
|
|
||||||
|
Saran isi:
|
||||||
|
|
||||||
|
- nama `JamShalat`
|
||||||
|
- visual ibadah harian
|
||||||
|
- warna utama teal / hijau kebiruan
|
||||||
|
- tanpa terlalu banyak teks
|
||||||
|
|
||||||
|
## Privacy policy
|
||||||
|
|
||||||
|
Wajib siapkan URL privacy policy yang bisa dibuka publik.
|
||||||
|
|
||||||
|
Isi minimum yang disarankan:
|
||||||
|
|
||||||
|
- data apa saja yang dipakai aplikasi
|
||||||
|
- lokasi dipakai untuk apa
|
||||||
|
- apakah data disimpan lokal atau dikirim ke server
|
||||||
|
- apakah ada notifikasi
|
||||||
|
- apakah ada login atau akun
|
||||||
|
- apakah ada pihak ketiga seperti API konten atau gambar
|
||||||
|
|
||||||
|
## Data safety
|
||||||
|
|
||||||
|
Isi form sesuai perilaku aplikasi yang benar-benar kamu upload.
|
||||||
|
|
||||||
|
Yang sebaiknya dijelaskan:
|
||||||
|
|
||||||
|
- lokasi dipakai untuk qibla dan jadwal shalat
|
||||||
|
- notifikasi dipakai untuk pengingat ibadah
|
||||||
|
- progres ibadah disimpan lokal di perangkat
|
||||||
|
- bila konten diambil dari API eksternal, jelaskan data apa yang dikirim dan untuk apa
|
||||||
|
- bila tidak ada login, nyatakan bahwa aplikasi tidak memerlukan akun
|
||||||
|
|
||||||
|
Catatan penting:
|
||||||
|
|
||||||
|
- isi Data safety jangan asal
|
||||||
|
- kalau aplikasi nanti mengirim koordinat lokasi ke server, itu harus dinyatakan
|
||||||
|
- kalau hanya dipakai di perangkat dan tidak disimpan/kirim, jelaskan sebagai penggunaan lokal
|
||||||
|
|
||||||
|
## Content rating
|
||||||
|
|
||||||
|
Jawab kuesioner dengan asumsi berikut:
|
||||||
|
|
||||||
|
- tidak ada kekerasan
|
||||||
|
- tidak ada judi
|
||||||
|
- tidak ada konten seksual
|
||||||
|
- tidak ada chat antar pengguna
|
||||||
|
- tidak ada konten buatan pengguna
|
||||||
|
- tidak ada iklan yang mengganggu isi utama
|
||||||
|
|
||||||
|
Biasanya hasilnya akan tetap rendah karena ini aplikasi ibadah, tetapi ikuti hasil kuesioner resmi di Play Console.
|
||||||
|
|
||||||
|
## App access
|
||||||
|
|
||||||
|
Jika aplikasi tidak punya login:
|
||||||
|
|
||||||
|
- pilih bahwa tidak ada kredensial yang harus diberikan
|
||||||
|
- tulis bahwa semua fitur utama bisa diakses langsung
|
||||||
|
|
||||||
|
Jika nanti ada login:
|
||||||
|
|
||||||
|
- siapkan akun demo
|
||||||
|
- tulis langkah akses yang jelas di form App access
|
||||||
|
|
||||||
|
## Target audience
|
||||||
|
|
||||||
|
Saran:
|
||||||
|
|
||||||
|
- umum
|
||||||
|
- semua usia
|
||||||
|
|
||||||
|
Kalau kamu ingin sangat aman, gunakan bahasa yang netral dan hindari klaim yang berlebihan.
|
||||||
|
|
||||||
|
## Store metadata yang disarankan
|
||||||
|
|
||||||
|
### Nama
|
||||||
|
|
||||||
|
`JamShalat`
|
||||||
|
|
||||||
|
### Ringkasan
|
||||||
|
|
||||||
|
Teman ibadah harian untuk shalat, Qur'an, dzikir, doa, dan laporan progres.
|
||||||
|
|
||||||
|
### Kata pembuka deskripsi
|
||||||
|
|
||||||
|
JamShalat adalah aplikasi pendamping ibadah harian untuk membantu pengguna menjaga rutinitas shalat, membaca Al-Qur'an, berdzikir, berdoa, dan memantau progres ibadah dengan lebih rapi.
|
||||||
|
|
||||||
|
## Checklist sebelum submit
|
||||||
|
|
||||||
|
- [x] nama aplikasi sudah final
|
||||||
|
- [x] package name sudah final
|
||||||
|
- [x] keystore rilis sudah tersedia
|
||||||
|
- [ ] `android/key.properties` sudah dibuat
|
||||||
|
- [ ] privacy policy URL sudah siap
|
||||||
|
- [ ] screenshot sudah siap
|
||||||
|
- [ ] feature graphic sudah siap
|
||||||
|
- [ ] data safety sudah diisi sesuai data nyata
|
||||||
|
- [ ] content rating sudah diisi
|
||||||
|
- [ ] AAB release sudah dibangun
|
||||||
|
- [ ] file `app-release.aab` sudah diupload ke track testing
|
||||||
|
|
||||||
127
docs/google-play-release-checklist.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Checklist Rilis Google Play
|
||||||
|
|
||||||
|
Dokumen ini menjelaskan langkah teknis dari repo ini sampai file `AAB` siap diupload ke Google Play Console.
|
||||||
|
|
||||||
|
## Status proyek saat ini
|
||||||
|
|
||||||
|
- [x] Nama aplikasi sudah diganti menjadi `JamShalat`
|
||||||
|
- [x] `applicationId` Android sudah diset ke `com.jamshalat.app`
|
||||||
|
- [x] File keystore rilis sudah ada di `android/app/upload-keystore.p12`
|
||||||
|
- [x] Konfigurasi signing release sudah disiapkan di `android/app/build.gradle.kts`
|
||||||
|
- [x] Versi aplikasi sudah ada di `pubspec.yaml`
|
||||||
|
- [x] Ikon dan branding aplikasi sudah ada
|
||||||
|
- [ ] File `android/key.properties` belum ada di repo
|
||||||
|
- [ ] URL privacy policy belum disiapkan
|
||||||
|
- [ ] Screenshot Play Store belum disiapkan
|
||||||
|
- [ ] Store listing dan form Play Console belum diisi
|
||||||
|
|
||||||
|
## Catatan penting soal signing
|
||||||
|
|
||||||
|
- Teks `Releases signed by Google Play` di Play Console berarti app signing dikelola Google Play.
|
||||||
|
- Itu berbeda dari upload key yang dipakai saat kamu membangun file `AAB` dari mesin lokal.
|
||||||
|
- `applicationId` ada di [android/app/build.gradle.kts](/Users/dwindown/Applications/jamshalat-diary/android/app/build.gradle.kts) dan saat ini bernilai `com.jamshalat.app`.
|
||||||
|
- File `android/key.properties` adalah file lokal di mesin rilis, bukan file yang di-upload ke Play Console.
|
||||||
|
- Pada repo ini, file keystore ada di `android/app/upload-keystore.p12`, dan isi `storeFile` di `key.properties` harus mengarah ke file itu dari folder `android/`.
|
||||||
|
|
||||||
|
## Yang perlu dipersiapkan
|
||||||
|
|
||||||
|
1. Siapkan kredensial signing release
|
||||||
|
- Buat file `android/key.properties`
|
||||||
|
- Isi dengan lokasi file keystore, alias, dan password
|
||||||
|
- Contoh isi:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
storeFile=../app/upload-keystore.p12
|
||||||
|
storePassword=PASSWORD_STORE_ANDA
|
||||||
|
keyAlias=upload
|
||||||
|
keyPassword=PASSWORD_KEY_ANDA
|
||||||
|
```
|
||||||
|
|
||||||
|
Catatan:
|
||||||
|
- alias pada keystore yang ada di repo ini adalah `upload`
|
||||||
|
- password `storePassword` dan `keyPassword` adalah password yang kamu tentukan saat membuat keystore
|
||||||
|
|
||||||
|
2. Naikkan versi aplikasi setiap kali rilis
|
||||||
|
- Edit `pubspec.yaml`
|
||||||
|
- Ubah `version` menjadi format `major.minor.patch+versionCode`
|
||||||
|
- Contoh:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 1.0.1+2
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Jalankan build release
|
||||||
|
- Dari root project, jalankan:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flutter pub get
|
||||||
|
flutter build appbundle --release
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Ambil file hasil build
|
||||||
|
- File yang diupload ke Play Console adalah:
|
||||||
|
|
||||||
|
```text
|
||||||
|
build/app/outputs/bundle/release/app-release.aab
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Upload ke Play Console
|
||||||
|
- Buka Google Play Console
|
||||||
|
- Masuk ke app `JamShalat`
|
||||||
|
- Upload `app-release.aab` ke track yang sesuai
|
||||||
|
- Saran alur:
|
||||||
|
- Internal testing untuk cek awal
|
||||||
|
- Closed testing jika diperlukan oleh akun baru
|
||||||
|
- Production setelah siap rilis
|
||||||
|
|
||||||
|
## Alur step by step
|
||||||
|
|
||||||
|
1. Pastikan file signing sudah lengkap
|
||||||
|
- `android/app/upload-keystore.p12` ada
|
||||||
|
- `android/key.properties` sudah dibuat
|
||||||
|
- password dan alias benar
|
||||||
|
|
||||||
|
2. Pastikan versi sudah dinaikkan
|
||||||
|
- Jangan upload `versionCode` yang sama dua kali
|
||||||
|
- Play Store menolak upload dengan `versionCode` yang sama atau lebih kecil
|
||||||
|
|
||||||
|
3. Build bundle release
|
||||||
|
- Jalankan `flutter build appbundle --release`
|
||||||
|
- Tunggu sampai selesai
|
||||||
|
|
||||||
|
4. Verifikasi file output
|
||||||
|
- Cari `build/app/outputs/bundle/release/app-release.aab`
|
||||||
|
- Inilah file utama untuk upload
|
||||||
|
|
||||||
|
5. Siapkan akun Play Console
|
||||||
|
- Login ke akun developer yang sudah diverifikasi
|
||||||
|
- Pastikan payment/profile/account sudah lengkap
|
||||||
|
|
||||||
|
6. Isi store listing
|
||||||
|
- Nama aplikasi
|
||||||
|
- Deskripsi singkat
|
||||||
|
- Deskripsi lengkap
|
||||||
|
- Ikon
|
||||||
|
- Screenshot
|
||||||
|
- Feature graphic
|
||||||
|
- Privacy policy
|
||||||
|
|
||||||
|
7. Isi form compliance
|
||||||
|
- Data safety
|
||||||
|
- Content rating
|
||||||
|
- Target audience
|
||||||
|
- App access jika ada login
|
||||||
|
|
||||||
|
8. Upload ke testing track dulu
|
||||||
|
- Disarankan jangan langsung produksi
|
||||||
|
- Tes install, buka app, login jika ada, dan semua fitur utama
|
||||||
|
|
||||||
|
9. Promosikan ke production
|
||||||
|
- Setelah semua aman, promote release ke production
|
||||||
|
|
||||||
|
## Catatan penting
|
||||||
|
|
||||||
|
- Karena `applicationId` saat ini sudah berubah menjadi `com.jamshalat.app`, upload ini akan dianggap sebagai app baru di Play Store jika sebelumnya ada app lain dengan package berbeda.
|
||||||
|
- Jangan gunakan APK untuk upload ke Play Store utama. Gunakan AAB.
|
||||||
|
- Kalau file `android/key.properties` belum ada, build lokal mungkin tetap jalan dengan debug signing, tetapi itu tidak boleh dipakai untuk upload ke Play Store.
|
||||||
|
- Untuk akun developer personal baru, biasanya perlu closed testing lebih dulu sebelum production. Cek aturan akun di Play Console jika diminta.
|
||||||
219
docs/google-play-release-tracks-id.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# Panduan Rilis per Track Google Play
|
||||||
|
|
||||||
|
Dokumen ini menjelaskan langkah detail untuk tiap jenis rilis: Internal Testing, Closed Testing, dan Production.
|
||||||
|
|
||||||
|
## Status proyek saat ini
|
||||||
|
|
||||||
|
- [x] Nama aplikasi sudah final: `JamShalat`
|
||||||
|
- [x] Package name Android sudah final: `com.jamshalat.app`
|
||||||
|
- [x] Keystore rilis sudah ada: `android/app/upload-keystore.p12`
|
||||||
|
- [ ] `android/key.properties` masih perlu disiapkan di mesin rilis
|
||||||
|
- [ ] URL privacy policy perlu disiapkan
|
||||||
|
- [ ] Screenshot Play Store perlu disiapkan
|
||||||
|
- [ ] Feature graphic perlu disiapkan
|
||||||
|
|
||||||
|
## Sebelum semua rilis
|
||||||
|
|
||||||
|
1. Naikkan versi aplikasi
|
||||||
|
- Edit `pubspec.yaml`
|
||||||
|
- Tambahkan `versionCode` baru
|
||||||
|
- Contoh:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 1.0.1+2
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Pastikan signing release aktif
|
||||||
|
- File `android/key.properties` harus ada
|
||||||
|
- Isi password dan alias harus benar
|
||||||
|
- Contoh isi yang sesuai dengan repo ini:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
storeFile=../app/upload-keystore.p12
|
||||||
|
storePassword=PASSWORD_STORE_ANDA
|
||||||
|
keyAlias=upload
|
||||||
|
keyPassword=PASSWORD_KEY_ANDA
|
||||||
|
```
|
||||||
|
|
||||||
|
Catatan:
|
||||||
|
- alias pada keystore yang ada di repo ini adalah `upload`
|
||||||
|
- password keystore tidak diambil dari Play Console, tapi dari saat keystore itu dibuat
|
||||||
|
|
||||||
|
3. Build bundle release
|
||||||
|
- Jalankan:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flutter pub get
|
||||||
|
flutter build appbundle --release
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Ambil file hasil build
|
||||||
|
- File upload ada di:
|
||||||
|
|
||||||
|
```text
|
||||||
|
build/app/outputs/bundle/release/app-release.aab
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. Internal Testing
|
||||||
|
|
||||||
|
Gunakan track ini untuk cek awal sebelum rilis lebih luas.
|
||||||
|
|
||||||
|
### Tujuan
|
||||||
|
|
||||||
|
- memastikan app bisa di-install
|
||||||
|
- memastikan login, beranda, jadwal, qibla, quran, dzikir, dan share berjalan
|
||||||
|
- menangkap crash atau bug awal
|
||||||
|
|
||||||
|
### Langkah
|
||||||
|
|
||||||
|
1. Buka Google Play Console
|
||||||
|
2. Pilih app `JamShalat`
|
||||||
|
3. Masuk ke `Testing` lalu `Internal testing`
|
||||||
|
4. Klik `Create new release`
|
||||||
|
5. Upload file `app-release.aab`
|
||||||
|
6. Tulis release notes
|
||||||
|
- contoh:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Perbaikan stabilitas, penyempurnaan tampilan, dan pembaruan fitur ibadah harian.
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Simpan release
|
||||||
|
8. Review ringkas halaman release
|
||||||
|
9. Klik `Save` atau `Publish` ke internal testing
|
||||||
|
10. Tambahkan tester internal jika diminta
|
||||||
|
11. Install dari link testing
|
||||||
|
12. Cek:
|
||||||
|
- buka aplikasi
|
||||||
|
- navigasi utama
|
||||||
|
- push notification
|
||||||
|
- audio murattal
|
||||||
|
- share gambar/tulisan
|
||||||
|
|
||||||
|
### Kapan dipakai
|
||||||
|
|
||||||
|
- setiap kali ada perubahan kecil
|
||||||
|
- saat ingin tes cepat setelah build baru
|
||||||
|
- sebelum closed testing
|
||||||
|
|
||||||
|
## 2. Closed Testing
|
||||||
|
|
||||||
|
Gunakan track ini untuk tes yang lebih serius dengan tester terbatas.
|
||||||
|
|
||||||
|
### Tujuan
|
||||||
|
|
||||||
|
- validasi sebelum produksi
|
||||||
|
- cek aplikasi pada beberapa device nyata
|
||||||
|
- kumpulkan feedback dari tester terbatas
|
||||||
|
|
||||||
|
### Langkah
|
||||||
|
|
||||||
|
1. Buka Google Play Console
|
||||||
|
2. Pilih app `JamShalat`
|
||||||
|
3. Masuk ke `Testing` lalu `Closed testing`
|
||||||
|
4. Buat tester list atau Google Group jika belum ada
|
||||||
|
5. Tambahkan alamat email tester
|
||||||
|
6. Klik `Create new release`
|
||||||
|
7. Upload `app-release.aab`
|
||||||
|
8. Tulis release notes
|
||||||
|
- contoh:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Rilis uji tertutup untuk validasi stabilitas, performa, dan kesiapan produksi.
|
||||||
|
```
|
||||||
|
|
||||||
|
9. Simpan release
|
||||||
|
10. Pastikan tester menerima link opt-in
|
||||||
|
11. Minta tester install dari link closed test
|
||||||
|
12. Tunggu hasil tes dan feedback
|
||||||
|
|
||||||
|
### Jika akun developer personal baru
|
||||||
|
|
||||||
|
Kalau Google Play Console meminta syarat tes 14 hari:
|
||||||
|
|
||||||
|
1. Jalankan closed testing sesuai aturan akun
|
||||||
|
2. Pastikan jumlah tester sesuai permintaan Google
|
||||||
|
3. Pastikan testing berjalan kontinu sesuai masa yang diminta
|
||||||
|
4. Setelah lolos, baru lanjut ke production
|
||||||
|
|
||||||
|
### Kapan dipakai
|
||||||
|
|
||||||
|
- sebelum production pertama kali
|
||||||
|
- saat perlu validasi lebih luas dari internal testing
|
||||||
|
|
||||||
|
## 3. Production
|
||||||
|
|
||||||
|
Gunakan track ini untuk rilis publik.
|
||||||
|
|
||||||
|
### Tujuan
|
||||||
|
|
||||||
|
- app tersedia untuk publik di Google Play
|
||||||
|
|
||||||
|
### Langkah
|
||||||
|
|
||||||
|
1. Pastikan internal testing dan closed testing sudah aman
|
||||||
|
2. Pastikan versionCode sudah naik
|
||||||
|
3. Pastikan listing sudah lengkap
|
||||||
|
- nama
|
||||||
|
- deskripsi
|
||||||
|
- screenshot
|
||||||
|
- feature graphic
|
||||||
|
- privacy policy
|
||||||
|
- data safety
|
||||||
|
- content rating
|
||||||
|
4. Masuk ke `Release` atau `Production` di Play Console
|
||||||
|
5. Klik `Create new release`
|
||||||
|
6. Upload file `app-release.aab`
|
||||||
|
7. Isi release notes publik
|
||||||
|
- contoh:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Rilis awal JamShalat dengan fitur jadwal shalat, Al-Qur'an, dzikir, kiblat, laporan ibadah, dan sharing ayat.
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Review summary
|
||||||
|
9. Pastikan tidak ada error atau warning fatal
|
||||||
|
10. Kirim ke review Google Play
|
||||||
|
11. Tunggu approval
|
||||||
|
12. Setelah disetujui, app akan tayang ke publik
|
||||||
|
|
||||||
|
### Kapan dipakai
|
||||||
|
|
||||||
|
- hanya ketika kamu siap publikasi
|
||||||
|
- jangan dipakai untuk tes harian
|
||||||
|
|
||||||
|
## Checklist per rilis
|
||||||
|
|
||||||
|
### Internal Testing
|
||||||
|
|
||||||
|
- [ ] versionCode dinaikkan
|
||||||
|
- [ ] `flutter build appbundle --release` berhasil
|
||||||
|
- [ ] `app-release.aab` siap
|
||||||
|
- [ ] release notes ditulis
|
||||||
|
- [ ] tester internal sudah ada
|
||||||
|
- [ ] install dari Play Console berhasil
|
||||||
|
|
||||||
|
### Closed Testing
|
||||||
|
|
||||||
|
- [ ] versionCode dinaikkan
|
||||||
|
- [ ] `app-release.aab` siap
|
||||||
|
- [ ] tester list sudah dibuat
|
||||||
|
- [ ] opt-in link sudah dibagikan
|
||||||
|
- [ ] feedback tester sudah dicek
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
- [ ] semua testing track aman
|
||||||
|
- [ ] store listing lengkap
|
||||||
|
- [ ] data safety diisi
|
||||||
|
- [ ] content rating selesai
|
||||||
|
- [ ] privacy policy aktif
|
||||||
|
- [ ] release notes publik ditulis
|
||||||
|
- [ ] review produksi sudah dikirim
|
||||||
|
|
||||||
|
## Catatan penting
|
||||||
|
|
||||||
|
- Jangan upload APK ke production. Pakai AAB.
|
||||||
|
- Jangan lupa menaikkan `versionCode` untuk setiap upload baru.
|
||||||
|
- Jika kamu mengganti `applicationId`, Play Console menganggapnya sebagai app baru.
|
||||||
|
- Simpan file keystore dan `android/key.properties` dengan aman.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Last updated: 2026-03-16
|
Last updated: 2026-03-16
|
||||||
Owner: Product + Mobile
|
Owner: Product + Mobile
|
||||||
Scope: `jamshalat_diary` (Flutter)
|
Scope: `JamShalat` (Flutter)
|
||||||
|
|
||||||
## Implementation Status (2026-03-17)
|
## Implementation Status (2026-03-17)
|
||||||
- Phase 1: Implemented.
|
- Phase 1: Implemented.
|
||||||
|
|||||||
109
docs/privacy-policy-gutenberg-id.html
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<h1>Kebijakan Privasi JamShalat</h1>
|
||||||
|
<p>Terakhir diperbarui: 19 Maret 2026</p>
|
||||||
|
|
||||||
|
<p>JamShalat menghormati privasi pengguna. Kebijakan ini menjelaskan data apa saja yang digunakan oleh aplikasi, bagaimana data tersebut diproses, dan untuk tujuan apa data dipakai.</p>
|
||||||
|
|
||||||
|
<p>Dengan menggunakan JamShalat, Anda menyetujui praktik yang dijelaskan dalam kebijakan ini.</p>
|
||||||
|
|
||||||
|
<h2>1. Informasi yang digunakan aplikasi</h2>
|
||||||
|
<p>JamShalat dapat menggunakan informasi berikut:</p>
|
||||||
|
<ul>
|
||||||
|
<li>lokasi perangkat</li>
|
||||||
|
<li>data ibadah yang disimpan di perangkat</li>
|
||||||
|
<li>pengaturan aplikasi</li>
|
||||||
|
<li>data notifikasi</li>
|
||||||
|
<li>data konten yang diambil dari layanan pihak ketiga</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>2. Lokasi perangkat</h2>
|
||||||
|
<p>JamShalat dapat menggunakan lokasi perangkat untuk:</p>
|
||||||
|
<ul>
|
||||||
|
<li>menampilkan arah kiblat</li>
|
||||||
|
<li>menghitung atau menampilkan jadwal shalat</li>
|
||||||
|
<li>membantu menyesuaikan informasi ibadah berdasarkan wilayah pengguna</li>
|
||||||
|
</ul>
|
||||||
|
<p>Lokasi dipakai hanya untuk fitur yang membutuhkan lokasi. Jika izin lokasi tidak diberikan, fitur yang bergantung pada lokasi mungkin tidak bekerja penuh.</p>
|
||||||
|
|
||||||
|
<h2>3. Penyimpanan data</h2>
|
||||||
|
<p>Sebagian besar data pengguna disimpan secara lokal di perangkat, termasuk:</p>
|
||||||
|
<ul>
|
||||||
|
<li>progres checklist ibadah</li>
|
||||||
|
<li>marka bacaan Al-Qur'an</li>
|
||||||
|
<li>dzikir counter</li>
|
||||||
|
<li>pengaturan tampilan</li>
|
||||||
|
<li>preferensi mode aplikasi</li>
|
||||||
|
</ul>
|
||||||
|
<p>Data lokal ini digunakan untuk menjaga pengalaman pengguna tetap konsisten di perangkat yang sama.</p>
|
||||||
|
|
||||||
|
<h2>4. Notifikasi</h2>
|
||||||
|
<p>JamShalat dapat menggunakan notifikasi untuk:</p>
|
||||||
|
<ul>
|
||||||
|
<li>pengingat adzan</li>
|
||||||
|
<li>pengingat iqamah</li>
|
||||||
|
<li>pengingat ibadah dan dzikir</li>
|
||||||
|
<li>notifikasi ringkasan atau pengingat lain yang relevan</li>
|
||||||
|
</ul>
|
||||||
|
<p>Notifikasi dipakai untuk membantu pengguna menjaga rutinitas ibadah.</p>
|
||||||
|
|
||||||
|
<h2>5. Konten dari layanan pihak ketiga</h2>
|
||||||
|
<p>JamShalat dapat mengambil data dari layanan pihak ketiga untuk menyediakan:</p>
|
||||||
|
<ul>
|
||||||
|
<li>jadwal shalat</li>
|
||||||
|
<li>konten Al-Qur'an</li>
|
||||||
|
<li>tafsir</li>
|
||||||
|
<li>dzikir</li>
|
||||||
|
<li>doa</li>
|
||||||
|
<li>hadits</li>
|
||||||
|
<li>gambar latar atau konten visual tertentu</li>
|
||||||
|
</ul>
|
||||||
|
<p>Layanan pihak ketiga yang dapat digunakan antara lain API konten dan API gambar.</p>
|
||||||
|
<p>JamShalat tidak menjual data pribadi pengguna.</p>
|
||||||
|
|
||||||
|
<h2>6. Data yang tidak dikumpulkan</h2>
|
||||||
|
<p>JamShalat tidak mengharuskan pengguna membuat akun untuk memakai fitur utama.</p>
|
||||||
|
<p>JamShalat tidak secara khusus meminta:</p>
|
||||||
|
<ul>
|
||||||
|
<li>nama lengkap</li>
|
||||||
|
<li>alamat email</li>
|
||||||
|
<li>nomor telepon</li>
|
||||||
|
<li>data kartu kredit</li>
|
||||||
|
<li>kontak pribadi</li>
|
||||||
|
</ul>
|
||||||
|
<p>Jika suatu saat ada fitur tambahan yang membutuhkan data lain, kebijakan ini akan diperbarui.</p>
|
||||||
|
|
||||||
|
<h2>7. Penggunaan data</h2>
|
||||||
|
<p>Data yang digunakan JamShalat dipakai untuk:</p>
|
||||||
|
<ul>
|
||||||
|
<li>menjalankan fitur aplikasi</li>
|
||||||
|
<li>menyimpan progres ibadah</li>
|
||||||
|
<li>menampilkan jadwal, arah kiblat, dan konten ibadah</li>
|
||||||
|
<li>mengirim pengingat yang dipilih pengguna</li>
|
||||||
|
<li>menjaga pengalaman pengguna tetap nyaman dan konsisten</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>8. Pembagian data</h2>
|
||||||
|
<p>JamShalat tidak membagikan data pribadi pengguna kepada pihak lain untuk tujuan iklan atau penjualan.</p>
|
||||||
|
<p>Data dapat diproses oleh layanan pihak ketiga yang memang dibutuhkan untuk menampilkan konten atau gambar, misalnya API jadwal shalat, API Al-Qur'an, atau layanan gambar.</p>
|
||||||
|
|
||||||
|
<h2>9. Penyimpanan dan retensi</h2>
|
||||||
|
<p>Data lokal disimpan di perangkat pengguna selama aplikasi masih digunakan atau sampai pengguna menghapus data aplikasi.</p>
|
||||||
|
<p>Jika pengguna menghapus aplikasi atau membersihkan data aplikasi, data lokal yang tersimpan di perangkat dapat ikut terhapus.</p>
|
||||||
|
|
||||||
|
<h2>10. Keamanan</h2>
|
||||||
|
<p>JamShalat berusaha menggunakan cara yang wajar untuk menjaga data pengguna. Namun, tidak ada metode penyimpanan atau transmisi data yang sepenuhnya bebas risiko.</p>
|
||||||
|
|
||||||
|
<h2>11. Hak pengguna</h2>
|
||||||
|
<p>Pengguna dapat:</p>
|
||||||
|
<ul>
|
||||||
|
<li>menolak izin lokasi atau notifikasi</li>
|
||||||
|
<li>menghapus data aplikasi dari perangkat</li>
|
||||||
|
<li>mencopot pemasangan aplikasi</li>
|
||||||
|
</ul>
|
||||||
|
<p>Jika izin tertentu ditolak, beberapa fitur mungkin tidak berfungsi penuh.</p>
|
||||||
|
|
||||||
|
<h2>12. Perubahan kebijakan</h2>
|
||||||
|
<p>Kebijakan privasi ini dapat diperbarui dari waktu ke waktu. Perubahan terbaru akan ditampilkan di halaman ini.</p>
|
||||||
|
|
||||||
|
<h2>13. Kontak</h2>
|
||||||
|
<p>Jika ada pertanyaan tentang kebijakan privasi ini, silakan hubungi pengembang aplikasi melalui kontak resmi yang disediakan di Play Store atau situs aplikasi.</p>
|
||||||
|
|
||||||
129
docs/privacy-policy-id.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# Kebijakan Privasi JamShalat
|
||||||
|
|
||||||
|
Terakhir diperbarui: 19 Maret 2026
|
||||||
|
|
||||||
|
Dokumen ini ditulis agar mudah dipaste ke editor CMS seperti Gutenberg.
|
||||||
|
|
||||||
|
## 1. Pendahuluan
|
||||||
|
|
||||||
|
JamShalat menghormati privasi pengguna. Kebijakan ini menjelaskan data apa saja yang digunakan oleh aplikasi, bagaimana data tersebut diproses, dan untuk tujuan apa data dipakai.
|
||||||
|
|
||||||
|
Dengan menggunakan JamShalat, Anda menyetujui praktik yang dijelaskan dalam kebijakan ini.
|
||||||
|
|
||||||
|
## 2. Informasi yang digunakan aplikasi
|
||||||
|
|
||||||
|
JamShalat dapat menggunakan informasi berikut:
|
||||||
|
|
||||||
|
- lokasi perangkat
|
||||||
|
- data ibadah yang disimpan di perangkat
|
||||||
|
- pengaturan aplikasi
|
||||||
|
- data notifikasi
|
||||||
|
- data konten yang diambil dari layanan pihak ketiga
|
||||||
|
|
||||||
|
## 3. Lokasi perangkat
|
||||||
|
|
||||||
|
JamShalat dapat menggunakan lokasi perangkat untuk:
|
||||||
|
|
||||||
|
- menampilkan arah kiblat
|
||||||
|
- menghitung atau menampilkan jadwal shalat
|
||||||
|
- membantu menyesuaikan informasi ibadah berdasarkan wilayah pengguna
|
||||||
|
|
||||||
|
Lokasi dipakai hanya untuk fitur yang membutuhkan lokasi. Jika izin lokasi tidak diberikan, fitur yang bergantung pada lokasi mungkin tidak bekerja penuh.
|
||||||
|
|
||||||
|
## 4. Penyimpanan data
|
||||||
|
|
||||||
|
Sebagian besar data pengguna disimpan secara lokal di perangkat, termasuk:
|
||||||
|
|
||||||
|
- progres checklist ibadah
|
||||||
|
- marka bacaan Al-Qur'an
|
||||||
|
- dzikir counter
|
||||||
|
- pengaturan tampilan
|
||||||
|
- preferensi mode aplikasi
|
||||||
|
|
||||||
|
Data lokal ini digunakan untuk menjaga pengalaman pengguna tetap konsisten di perangkat yang sama.
|
||||||
|
|
||||||
|
## 5. Notifikasi
|
||||||
|
|
||||||
|
JamShalat dapat menggunakan notifikasi untuk:
|
||||||
|
|
||||||
|
- pengingat adzan
|
||||||
|
- pengingat iqamah
|
||||||
|
- pengingat ibadah dan dzikir
|
||||||
|
- notifikasi ringkasan atau pengingat lain yang relevan
|
||||||
|
|
||||||
|
Notifikasi dipakai untuk membantu pengguna menjaga rutinitas ibadah.
|
||||||
|
|
||||||
|
## 6. Konten dari layanan pihak ketiga
|
||||||
|
|
||||||
|
JamShalat dapat mengambil data dari layanan pihak ketiga untuk menyediakan:
|
||||||
|
|
||||||
|
- jadwal shalat
|
||||||
|
- konten Al-Qur'an
|
||||||
|
- tafsir
|
||||||
|
- dzikir
|
||||||
|
- doa
|
||||||
|
- hadits
|
||||||
|
- gambar latar atau konten visual tertentu
|
||||||
|
|
||||||
|
Layanan pihak ketiga yang dapat digunakan antara lain API konten dan API gambar.
|
||||||
|
|
||||||
|
JamShalat tidak menjual data pribadi pengguna.
|
||||||
|
|
||||||
|
## 7. Data yang tidak dikumpulkan
|
||||||
|
|
||||||
|
JamShalat tidak mengharuskan pengguna membuat akun untuk memakai fitur utama.
|
||||||
|
|
||||||
|
JamShalat tidak secara khusus meminta:
|
||||||
|
|
||||||
|
- nama lengkap
|
||||||
|
- alamat email
|
||||||
|
- nomor telepon
|
||||||
|
- data kartu kredit
|
||||||
|
- kontak pribadi
|
||||||
|
|
||||||
|
Jika suatu saat ada fitur tambahan yang membutuhkan data lain, kebijakan ini akan diperbarui.
|
||||||
|
|
||||||
|
## 8. Penggunaan data
|
||||||
|
|
||||||
|
Data yang digunakan JamShalat dipakai untuk:
|
||||||
|
|
||||||
|
- menjalankan fitur aplikasi
|
||||||
|
- menyimpan progres ibadah
|
||||||
|
- menampilkan jadwal, arah kiblat, dan konten ibadah
|
||||||
|
- mengirim pengingat yang dipilih pengguna
|
||||||
|
- menjaga pengalaman pengguna tetap nyaman dan konsisten
|
||||||
|
|
||||||
|
## 9. Pembagian data
|
||||||
|
|
||||||
|
JamShalat tidak membagikan data pribadi pengguna kepada pihak lain untuk tujuan iklan atau penjualan.
|
||||||
|
|
||||||
|
Data dapat diproses oleh layanan pihak ketiga yang memang dibutuhkan untuk menampilkan konten atau gambar, misalnya API jadwal shalat, API Al-Qur'an, atau layanan gambar.
|
||||||
|
|
||||||
|
## 10. Penyimpanan dan retensi
|
||||||
|
|
||||||
|
Data lokal disimpan di perangkat pengguna selama aplikasi masih digunakan atau sampai pengguna menghapus data aplikasi.
|
||||||
|
|
||||||
|
Jika pengguna menghapus aplikasi atau membersihkan data aplikasi, data lokal yang tersimpan di perangkat dapat ikut terhapus.
|
||||||
|
|
||||||
|
## 11. Keamanan
|
||||||
|
|
||||||
|
JamShalat berusaha menggunakan cara yang wajar untuk menjaga data pengguna. Namun, tidak ada metode penyimpanan atau transmisi data yang sepenuhnya bebas risiko.
|
||||||
|
|
||||||
|
## 12. Hak pengguna
|
||||||
|
|
||||||
|
Pengguna dapat:
|
||||||
|
|
||||||
|
- menolak izin lokasi atau notifikasi
|
||||||
|
- menghapus data aplikasi dari perangkat
|
||||||
|
- mencopot pemasangan aplikasi
|
||||||
|
|
||||||
|
Jika izin tertentu ditolak, beberapa fitur mungkin tidak berfungsi penuh.
|
||||||
|
|
||||||
|
## 13. Perubahan kebijakan
|
||||||
|
|
||||||
|
Kebijakan privasi ini dapat diperbarui dari waktu ke waktu. Perubahan terbaru akan ditampilkan di halaman ini.
|
||||||
|
|
||||||
|
## 14. Kontak
|
||||||
|
|
||||||
|
Jika ada pertanyaan tentang kebijakan privasi ini, silakan hubungi pengembang aplikasi melalui kontak resmi yang disediakan di Play Store atau situs aplikasi.
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Jamshalat Diary — Handoff Document
|
# JamShalat — Handoff Document
|
||||||
|
|
||||||
> Last updated: 2026-03-15
|
> Last updated: 2026-03-15
|
||||||
|
|
||||||
|
|||||||
@@ -372,7 +372,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -388,7 +388,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
@@ -420,7 +420,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
@@ -551,7 +551,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -573,7 +573,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 949 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 12 KiB |
@@ -7,7 +7,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Jamshalat Diary</string>
|
<string>JamShalat</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>jamshalat_diary</string>
|
<string>JamShalat</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
|
|||||||
final themeMode = ref.watch(themeProvider);
|
final themeMode = ref.watch(themeProvider);
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
title: 'Jamshalat Diary',
|
title: 'JamShalat',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: AppTheme.light,
|
theme: AppTheme.light,
|
||||||
darkTheme: AppTheme.dark,
|
darkTheme: AppTheme.dark,
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:lucide_icons/lucide_icons.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import '../../app/icons/app_icons.dart';
|
import '../../app/icons/app_icons.dart';
|
||||||
import '../../app/theme/app_colors.dart';
|
import '../../app/theme/app_colors.dart';
|
||||||
import '../../core/widgets/arabic_text.dart';
|
import '../../core/widgets/arabic_text.dart';
|
||||||
|
|
||||||
|
const MethodChannel _androidShareChannel = MethodChannel(
|
||||||
|
'com.jamshalat.app/share',
|
||||||
|
);
|
||||||
|
|
||||||
String buildAyatShareText(Map<String, dynamic> ayat) {
|
String buildAyatShareText(Map<String, dynamic> ayat) {
|
||||||
final arabic = (ayat['teksArab'] ?? '').toString().trim();
|
final arabic = (ayat['teksArab'] ?? '').toString().trim();
|
||||||
final translation = (ayat['teksIndonesia'] ?? '').toString().trim();
|
final translation = (ayat['teksIndonesia'] ?? '').toString().trim();
|
||||||
@@ -24,7 +29,7 @@ String buildAyatShareText(Map<String, dynamic> ayat) {
|
|||||||
if (arabic.isNotEmpty) arabic,
|
if (arabic.isNotEmpty) arabic,
|
||||||
if (translation.isNotEmpty) '"$translation"',
|
if (translation.isNotEmpty) '"$translation"',
|
||||||
reference,
|
reference,
|
||||||
'Dibagikan dari Jam Shalat Diary',
|
'Dibagikan dari JamShalat',
|
||||||
];
|
];
|
||||||
|
|
||||||
return parts.join('\n\n');
|
return parts.join('\n\n');
|
||||||
@@ -50,11 +55,7 @@ Future<void> showAyatShareSheet(
|
|||||||
try {
|
try {
|
||||||
final pngBytes = await _captureAyatShareCardPng(context, ayat);
|
final pngBytes = await _captureAyatShareCardPng(context, ayat);
|
||||||
final file = await _writeAyatShareImage(pngBytes);
|
final file = await _writeAyatShareImage(pngBytes);
|
||||||
await Share.shareXFiles(
|
await _shareAyatImage(file);
|
||||||
[XFile(file.path)],
|
|
||||||
text: 'Ayat Hari Ini',
|
|
||||||
subject: 'Ayat Hari Ini',
|
|
||||||
);
|
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
@@ -224,12 +225,40 @@ Future<Uint8List> _captureAyatShareCardPng(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<File> _writeAyatShareImage(Uint8List pngBytes) async {
|
Future<File> _writeAyatShareImage(Uint8List pngBytes) async {
|
||||||
final directory = await Directory.systemTemp.createTemp('jamshalat_ayat_');
|
final tempDirectory = await getTemporaryDirectory();
|
||||||
final file = File('${directory.path}/ayat_hari_ini.png');
|
final shareDirectory =
|
||||||
|
Directory('${tempDirectory.path}/share_plus/jamshalat');
|
||||||
|
if (!await shareDirectory.exists()) {
|
||||||
|
await shareDirectory.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final file = File('${shareDirectory.path}/ayat_hari_ini_$timestamp.png');
|
||||||
await file.writeAsBytes(pngBytes, flush: true);
|
await file.writeAsBytes(pngBytes, flush: true);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _shareAyatImage(File file) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
await _androidShareChannel.invokeMethod<void>('shareImage', {
|
||||||
|
'path': file.path,
|
||||||
|
'mimeType': 'image/png',
|
||||||
|
'chooserTitle': 'Bagikan ayat',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Share.shareXFiles(
|
||||||
|
[
|
||||||
|
XFile(
|
||||||
|
file.path,
|
||||||
|
mimeType: 'image/png',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
fileNameOverrides: const ['jamshalat_ayat_hari_ini.png'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class _AyatShareCard extends StatelessWidget {
|
class _AyatShareCard extends StatelessWidget {
|
||||||
const _AyatShareCard({
|
const _AyatShareCard({
|
||||||
required this.ayat,
|
required this.ayat,
|
||||||
@@ -447,7 +476,7 @@ class _AyatShareCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Jam Shalat Diary',
|
'JamShalat',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
|
|||||||
23
lib/core/widgets/bottom_sheet_content_padding.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
EdgeInsets bottomSheetContentPadding(
|
||||||
|
BuildContext context, {
|
||||||
|
double horizontal = 16,
|
||||||
|
double top = 20,
|
||||||
|
double bottom = 20,
|
||||||
|
}) {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final bottomInset = math.max(
|
||||||
|
mediaQuery.viewInsets.bottom,
|
||||||
|
mediaQuery.viewPadding.bottom,
|
||||||
|
);
|
||||||
|
|
||||||
|
return EdgeInsets.fromLTRB(
|
||||||
|
horizontal,
|
||||||
|
top,
|
||||||
|
horizontal,
|
||||||
|
bottom + bottomInset,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:hive_flutter/hive_flutter.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/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.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/services/muslim_api_service.dart';
|
import '../../../data/services/muslim_api_service.dart';
|
||||||
@@ -91,9 +92,8 @@ class _DoaScreenState extends State<DoaScreen> {
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:lucide_icons/lucide_icons.dart';
|
|||||||
|
|
||||||
import '../../../app/theme/app_colors.dart';
|
import '../../../app/theme/app_colors.dart';
|
||||||
import '../../../core/widgets/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.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';
|
||||||
@@ -642,9 +643,8 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:hive_flutter/hive_flutter.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/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.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/services/muslim_api_service.dart';
|
import '../../../data/services/muslim_api_service.dart';
|
||||||
@@ -96,9 +97,8 @@ class _HaditsScreenState extends State<HaditsScreen> {
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class QiblaScreen extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _QiblaScreenState extends ConsumerState<QiblaScreen> {
|
class _QiblaScreenState extends ConsumerState<QiblaScreen> {
|
||||||
static const _geomagneticChannel =
|
static const _geomagneticChannel =
|
||||||
MethodChannel('com.jamshalat.diary/geomagnetic');
|
MethodChannel('com.jamshalat.app/geomagnetic');
|
||||||
static const double _alignmentThreshold = 3.0;
|
static const double _alignmentThreshold = 3.0;
|
||||||
|
|
||||||
// Fallback simulated data for environments without compass hardware (like macOS emulator)
|
// Fallback simulated data for environments without compass hardware (like macOS emulator)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import '../../../app/theme/app_colors.dart';
|
import '../../../app/theme/app_colors.dart';
|
||||||
import '../../../core/widgets/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.dart';
|
||||||
import '../../../data/local/hive_boxes.dart';
|
import '../../../data/local/hive_boxes.dart';
|
||||||
import '../../../data/local/models/quran_bookmark.dart';
|
import '../../../data/local/models/quran_bookmark.dart';
|
||||||
import '../../../data/local/models/app_settings.dart';
|
import '../../../data/local/models/app_settings.dart';
|
||||||
@@ -25,7 +26,8 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
|||||||
bool _showLatin = true;
|
bool _showLatin = true;
|
||||||
bool _showTerjemahan = true;
|
bool _showTerjemahan = true;
|
||||||
final Map<int, Future<Map<String, dynamic>?>> _surahFutureCache = {};
|
final Map<int, Future<Map<String, dynamic>?>> _surahFutureCache = {};
|
||||||
final Map<dynamic, Future<_ResolvedBookmarkContent?>> _bookmarkFutureCache = {};
|
final Map<dynamic, Future<_ResolvedBookmarkContent?>> _bookmarkFutureCache =
|
||||||
|
{};
|
||||||
|
|
||||||
String _readingRoute(int surahId, int verseId) {
|
String _readingRoute(int surahId, int verseId) {
|
||||||
final isSimple =
|
final isSimple =
|
||||||
@@ -54,9 +56,8 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -147,7 +148,8 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
|
|||||||
Future<_ResolvedBookmarkContent?> _getResolvedBookmarkContent(
|
Future<_ResolvedBookmarkContent?> _getResolvedBookmarkContent(
|
||||||
QuranBookmark bookmark,
|
QuranBookmark bookmark,
|
||||||
) {
|
) {
|
||||||
final bookmarkKey = bookmark.key ?? '${bookmark.surahId}_${bookmark.verseId}';
|
final bookmarkKey =
|
||||||
|
bookmark.key ?? '${bookmark.surahId}_${bookmark.verseId}';
|
||||||
return _bookmarkFutureCache.putIfAbsent(
|
return _bookmarkFutureCache.putIfAbsent(
|
||||||
bookmarkKey,
|
bookmarkKey,
|
||||||
() => _loadResolvedBookmarkContent(bookmark),
|
() => _loadResolvedBookmarkContent(bookmark),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:hive_flutter/hive_flutter.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/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.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/services/muslim_api_service.dart';
|
import '../../../data/services/muslim_api_service.dart';
|
||||||
@@ -193,9 +194,8 @@ class _QuranEnrichmentScreenState extends State<QuranEnrichmentScreen>
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -919,7 +919,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen>
|
|||||||
final url = _unsplashPhoto!['photographerUrl'];
|
final url = _unsplashPhoto!['photographerUrl'];
|
||||||
if (url != null && url.isNotEmpty) {
|
if (url != null && url.isNotEmpty) {
|
||||||
launchUrl(Uri.parse(
|
launchUrl(Uri.parse(
|
||||||
'$url?utm_source=jamshalat_diary&utm_medium=referral'));
|
'$url?utm_source=jamshalat&utm_medium=referral'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:intl/intl.dart';
|
|||||||
import '../../../app/theme/app_colors.dart';
|
import '../../../app/theme/app_colors.dart';
|
||||||
import '../../../core/services/app_audio_player.dart';
|
import '../../../core/services/app_audio_player.dart';
|
||||||
import '../../../core/widgets/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.dart';
|
||||||
import '../../../data/local/hive_boxes.dart';
|
import '../../../data/local/hive_boxes.dart';
|
||||||
import '../../../data/local/models/quran_bookmark.dart';
|
import '../../../data/local/models/quran_bookmark.dart';
|
||||||
import '../../../data/local/models/app_settings.dart';
|
import '../../../data/local/models/app_settings.dart';
|
||||||
@@ -845,9 +846,8 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:lucide_icons/lucide_icons.dart';
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import '../../../app/theme/app_colors.dart';
|
import '../../../app/theme/app_colors.dart';
|
||||||
import '../../../core/widgets/arabic_text.dart';
|
import '../../../core/widgets/arabic_text.dart';
|
||||||
|
import '../../../core/widgets/bottom_sheet_content_padding.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';
|
||||||
@@ -55,9 +56,8 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
|
|||||||
),
|
),
|
||||||
builder: (ctx) => StatefulBuilder(
|
builder: (ctx) => StatefulBuilder(
|
||||||
builder: (context, setModalState) {
|
builder: (context, setModalState) {
|
||||||
final keyboardInset = MediaQuery.of(context).viewInsets.bottom;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16, 20, 16, 20 + keyboardInset),
|
padding: bottomSheetContentPadding(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ void main() async {
|
|||||||
));
|
));
|
||||||
|
|
||||||
await JustAudioBackground.init(
|
await JustAudioBackground.init(
|
||||||
androidNotificationChannelId: 'com.jamshalat.diary.audio',
|
androidNotificationChannelId: 'com.jamshalat.app.audio',
|
||||||
androidNotificationChannelName: 'Murattal Playback',
|
androidNotificationChannelName: 'Murattal Playback',
|
||||||
androidNotificationOngoing: true,
|
androidNotificationOngoing: true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -479,7 +479,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jamshalat_diary.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jamshalat_diary";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jamshalat_diary.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jamshalat_diary";
|
||||||
@@ -494,7 +494,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jamshalat_diary.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jamshalat_diary";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jamshalat_diary.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jamshalat_diary";
|
||||||
@@ -509,7 +509,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jamshalat_diary.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jamshalat_diary";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jamshalat_diary.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jamshalat_diary";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
PRODUCT_NAME = jamshalat_diary
|
PRODUCT_NAME = jamshalat_diary
|
||||||
|
|
||||||
// The application's bundle identifier
|
// The application's bundle identifier
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.jamshalatDiary
|
PRODUCT_BUNDLE_IDENTIFIER = com.jamshalat.app
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2026 com.jamshalat. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2026 com.jamshalat. All rights reserved.
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>JamShalat</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>JamShalat</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|||||||
@@ -870,7 +870,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ dependencies:
|
|||||||
flutter_dotenv: ^5.1.0
|
flutter_dotenv: ^5.1.0
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
share_plus: ^10.1.4
|
share_plus: ^10.1.4
|
||||||
|
path_provider: ^2.1.5
|
||||||
url_launcher: ^6.2.5
|
url_launcher: ^6.2.5
|
||||||
lucide_icons: ^0.257.0
|
lucide_icons: ^0.257.0
|
||||||
hugeicons: ^1.1.5
|
hugeicons: ^1.1.5
|
||||||
|
|||||||
@@ -18,18 +18,18 @@
|
|||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
<meta name="description" content="A new Flutter project.">
|
<meta name="description" content="JamShalat Muslim daily worship companion.">
|
||||||
|
|
||||||
<!-- iOS meta tags & icons -->
|
<!-- iOS meta tags & icons -->
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="jamshalat_diary">
|
<meta name="apple-mobile-web-app-title" content="JamShalat">
|
||||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
<title>jamshalat_diary</title>
|
<title>JamShalat</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "jamshalat_diary",
|
"name": "JamShalat",
|
||||||
"short_name": "jamshalat_diary",
|
"short_name": "JamShalat",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#0175C2",
|
"background_color": "#0175C2",
|
||||||
"theme_color": "#0175C2",
|
"theme_color": "#0175C2",
|
||||||
"description": "A new Flutter project.",
|
"description": "JamShalat Muslim daily worship companion.",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
"icons": [
|
"icons": [
|
||||||
|
|||||||