first week launch

This commit is contained in:
Wowieee4 2026-04-22 20:57:19 +07:00
commit 65b87d3d9d
170 changed files with 7378 additions and 0 deletions

94
.github/workflows/update-badge.yml vendored Normal file
View File

@ -0,0 +1,94 @@
name: Update README Badge
on:
push:
branches: ["**"]
jobs:
update-badge:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update README badges
run: |
python3 << 'EOF'
import re
import subprocess
from datetime import datetime, timezone, timedelta
# Ambil info dari git log
author = subprocess.check_output(
["git", "log", "-1", "--format=%ae"],
text=True
).strip()
# Ambil actor dari env (GitHub username)
import os
actor = os.environ.get("GITHUB_ACTOR", "unknown")
# Waktu commit terakhir dalam WIB (UTC+7)
ts = subprocess.check_output(
["git", "log", "-1", "--format=%ct"],
text=True
).strip()
wib = datetime.fromtimestamp(int(ts), tz=timezone(timedelta(hours=7)))
time_str = wib.strftime("%d/%m/%Y %H:%M WIB")
# Format untuk shields.io badge
badge_time = time_str.replace(" ", "_").replace("/", "%2F").replace(":", "%3A")
badge_author = actor.replace("-", "--")
profile_url = f"https://github.com/{actor}"
# Badge markdown yang bersih
new_time_badge = f"![Last Updated](https://img.shields.io/badge/Last_Updated-{badge_time}-blue?style=for-the-badge)"
new_author_badge = f"![Author](https://img.shields.io/badge/By-{badge_author}-brightgreen?style=for-the-badge)"
with open("README.md", "r", encoding="utf-8") as f:
content = f.read()
# Replace Last Updated badge
content = re.sub(
r'!\[Last Updated\]\(https://img\.shields\.io/badge/Last_Updated-[^)]+\)',
new_time_badge,
content
)
# Replace Author badge (dengan atau tanpa link)
content = re.sub(
r'\[!\[Author\]\(https://img\.shields\.io/badge/By-[^)]+\)\]\([^)]+\)',
new_author_badge,
content
)
# Fallback: tanpa link wrapper
content = re.sub(
r'!\[Author\]\(https://img\.shields\.io/badge/By-[^)]+\)',
new_author_badge,
content
)
with open("README.md", "w", encoding="utf-8") as f:
f.write(content)
print(f"Updated: {time_str} | Author: {actor}")
EOF
env:
GITHUB_ACTOR: ${{ github.actor }}
- name: Commit updated README
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet README.md; then
echo "No changes, skipping."
else
git add README.md
git commit -m "chore: update badge [skip ci]"
git push
fi

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 陈文悠
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

194
README.md Normal file
View File

@ -0,0 +1,194 @@
<div align="center">
> ⚠️ **WORK IN PROGRESS (WIP)** ⚠️
> *This repository is currently under active development. All code, data, and structures are subject to continuous changes.*
<br>
<img src="assets/gambar.png" alt="WalkGuide Banner" width="720"/>
# WalkGuide: AI-Powered Navigation
**Integrated Mobile Application Project**
*Flutter Mobile Frontend × Spring Boot Backend × OOAD*
### Group Members
| Name | NIM | Responsibility |
|------|-----|---------------|
| Bambang Herlambang | 5803024019 | - |
| Jap Robertus | 5803024004 | - |
| Evan William | 5803024001 | Backend Engineer (Spring Boot API & Flutter) |
[![Flutter](https://img.shields.io/badge/Flutter-Clean_Architecture-02569B?style=flat-square&logo=flutter&logoColor=white)](https://flutter.dev/)
[![Spring Boot](https://img.shields.io/badge/Spring_Boot-REST_API-6DB33F?style=flat-square&logo=spring&logoColor=white)](https://spring.io/projects/spring-boot)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-Database-4169E1?style=flat-square&logo=postgresql&logoColor=white)](https://www.postgresql.org/)
[![License](https://img.shields.io/badge/License-MIT-22c55e?style=flat-square)](LICENSE)
![Last Updated](https://img.shields.io/badge/Last_Updated-WIP-blue?style=for-the-badge)
![Author](https://img.shields.io/badge/By-evan--william-brightgreen?style=for-the-badge)
[**System Architecture**](#system-architecture) · [**Implementations**](#implementations) · [**Results**](#results) · [**Weekly Progress**](#weekly-progress)
</div>
---
## Overview — WalkGuide System
**Core Objective:** How can we build an ultra-low latency, accessible navigation system for visually impaired users while providing real-time oversight for their guardians?
This project implements a dual-interface mobile application. The system relies on **On-Device AI (TFLite)** to eliminate network latency during obstacle detection, paired with a robust **Spring Boot backend** for secure authentication and guardian-user pairing.
---
## System Architecture
The study follows a strict three-pillar enterprise structure:
**Pillar 1 — OOAD (Object-Oriented Analysis & Design):** Comprehensive modeling using Use Case, Class, Sequence, and ERD diagrams. The codebase strictly implements ≥ 4 GoF Design Patterns (e.g., Builder, Singleton, Repository, Strategy).
**Pillar 2 — Flutter (Mobile Frontend):** Implements Clean Architecture (Domain, Data, Presentation layers) with BLoC/Riverpod for state management. Uses `Dio` for secure HTTP communication.
**Pillar 3 — Spring Boot (Backend API):** A layered architecture (Controller → Service → Repository) powered by Java 17+. Features JWT-based Role-Based Access Control (RBAC) and standardized `ApiResponse` envelopes.
---
## Implementations
### Design A — User Mode (Visually Impaired)
An accessibility-first interface that opens directly to an active camera view.
- **On-Device AI:** Obstacle detection using TFLite runs locally to ensure zero network latency.
- **Hardware Mapping:** Physical volume buttons mapped to critical actions (e.g., auto-accept guardian calls).
- **Voice Commands:** Integrated TTS and speech recognition for hands-free navigation.
### Design B — Guardian Mode (Admin)
A command center dashboard for oversight and configuration.
- **Real-time Monitoring:** Tracks user status and device battery.
- **Remote Configuration:** Adjusts AI sensitivity and sets hardware shortcuts for the paired User device.
- **Quick Actions:** One-tap voice conferencing and emergency overrides.
---
## Metrics
| Metric | Instrument | Target Threshold |
|--------|-----------|------|
| API Throughput | Apache JMeter / k6 | ≥ 100 req/s under load |
| API p95 Latency | Apache JMeter / k6 | < 500ms |
| UI Frame Rate | Flutter DevTools | ≥ 90% frames < 16ms |
| Code Coverage | JaCoCo | ≥ 70% (Service & Controller) |
---
## Technical Parameters
**Backend Phase (Spring Boot):**
- Environment: Java 17+, PostgreSQL / H2 Database, Flyway Migrations.
- Security: Spring Security, JWT (Access + Refresh Tokens).
- Documentation: OpenAPI 3.0 (Swagger UI).
**Mobile Phase (Flutter):**
- Environment: Flutter 3.x, physical Android device testing (Profile Mode).
- Storage: `flutter_secure_storage` for JWT.
- Routing: GoRouter with role-based authenticated routes.
---
## Repository Structure
> 🚨 **DISCLAIMER:** This repository is in active development. The directory tree below is a **conceptual reference snapshot** of the intended architecture. It **does not** represent the exact, current state of the files.
```text
/
├── walkguide-backend/ # [Spring Boot Engine]
│ ├── src/main/java/com/walkguide/
│ │ ├── config/ # Security & Swagger configs
│ │ ├── controller/ # REST Endpoints
│ │ ├── dto/ # AuthRequest & ApiResponse
│ │ ├── entity/ # JPA Entities
│ │ └── exception/ # GlobalExceptionHandler
│ └── pom.xml
├── walkguide-mobile/ # [Flutter Frontend]
│ ├── lib/
│ │ ├── core/ # API Service, Storage, Failures
│ │ ├── features/
│ │ │ ├── auth/ # Clean Arch: domain, data, presentation
│ │ │ └── home/ # User & Guardian Dashboards
│ │ └── main.dart
│ └── pubspec.yaml
├── ooad-docs/ # [Design Artifacts]
│ ├── diagrams/ # PlantUML / draw.io exports
│ └── Traceability_Matrix.md
└── README.md
````
-----
## Quick Start (WIP)
```bash
# 1. Clone the repository
git clone [https://github.com/YourGroup/walkguide-final-exam.git](https://github.com/YourGroup/walkguide-final-exam.git)
# 2. Run Backend (Spring Boot)
cd walkguide-backend
./mvnw spring-boot:run
# 3. Run Mobile (Flutter) - Connect to local backend
cd ../walkguide-mobile
flutter pub get
flutter run
```
-----
## Results
> ⏳ **Work In Progress:** Results are populated as benchmarking phases are completed.
| Metric | Baseline | Final Optimized | Status |
|--------|-----------------|----------------------|---------|
| Cold Start Time | *Pending* | *Pending* | ⏳ |
| Memory Leak (10 Navs)| *Pending* | *Pending* | ⏳ |
| API Error Rate | *Pending* | *Pending* | ⏳ |
-----
## Weekly Progress
| Week | Target | Status |
|------|--------|--------|
| 1 | Topic proposal, Use Case definitions, Repo setup | ✅ Done |
| 2-3 | OOAD diagrams, OpenAPI YAML drafted | 🔄 In Progress |
| 4 | Clean Architecture & Spring Boot layers implemented | ⏳ Pending |
| 5 | Mid-sprint benchmarking (Flutter + Backend) | ⏳ Pending |
| 6-7 | Feature freeze, AI integration, Integration testing | ⏳ Pending |
| 8 | Final benchmarks, Report writing, Demo Video | ⏳ Pending |
-----
## Core Objectives
**O₁ (Performance):** The dual-platform architecture must maintain a 60fps UI frame rate while handling concurrent backend requests without exceeding 500ms latency.
**O₂ (Accessibility):** The User mode must require zero visual interaction post-login, relying entirely on haptics, voice, and physical button mapping.
**O₃ (Traceability):** Every major feature implementation must be directly traceable back to the pre-development OOAD artifacts and GoF design patterns.
-----
## License
Distributed under the [MIT License](https://www.google.com/search?q=LICENSE).
-----
*Final Exam: Integrated Mobile Application Project* <br>
*Flutter × Spring Boot × Object-Oriented Analysis and Design*
```

View File

@ -0,0 +1,23 @@
@echo off
echo ================================
echo Flutter + Android Cleaner
echo ================================
echo.
echo [1/4] Cleaning Flutter pub cache...
call "D:\Tools\Flutter\flutter\bin\flutter.bat" pub cache clean
echo.
echo [2/4] Cleaning Gradle cache...
rd /s /q "%USERPROFILE%\.gradle\caches" 2>nul
echo Gradle cache cleared!
echo.
echo [3/4] Cleaning pub-cache packages...
rd /s /q "%USERPROFILE%\.pub-cache\hosted" 2>nul
echo Pub cache cleared!
echo.
echo [4/4] Done! Restart CMD untuk apply perubahan PATH.
echo.
pause

View File

@ -0,0 +1,43 @@
@echo off
echo ================================
echo Gradle Cache Cleaner
echo ================================
echo.
set GRADLE_CACHE=C:\Users\Evan\.gradle\caches
echo Lokasi: %GRADLE_CACHE%
echo.
:: Caches build sementara
echo [1/5] Cleaning build cache...
rd /s /q "%GRADLE_CACHE%\build-cache-1" 2>nul
echo Done!
:: Module cache (redownload otomatis kalau dibutuhkan)
echo [2/5] Cleaning modules cache...
rd /s /q "%GRADLE_CACHE%\modules-2\files-2.1" 2>nul
echo Done!
:: Transform cache hasil konversi library
echo [3/5] Cleaning transforms cache...
rd /s /q "%GRADLE_CACHE%\transforms-3" 2>nul
echo Done!
:: Temporary junk files
echo [4/5] Cleaning junk/tmp files...
rd /s /q "%GRADLE_CACHE%\tmp" 2>nul
del /q "%GRADLE_CACHE%\*.lock" 2>nul
echo Done!
:: Journal logs gradle
echo [5/5] Cleaning journal logs...
rd /s /q "%GRADLE_CACHE%\journal-1" 2>nul
echo Done!
echo.
echo ================================
echo Selesai! Semua cache bersih.
echo ================================
echo.
pause

11
additional/Note.txt Normal file
View File

@ -0,0 +1,11 @@
Things to add:
- AI nya di in device dan jangan di server kalua bisa sebisa mungkin biar tidak ada latency.
- Tambahkan fitur2 yg bisa lewat dari click button HP dan nyala
- Advanced fitur bisa ambil yg animation , push notif, atau jg:
A. Angkat call dari guardian dengan click button HP missal volume or what.
B. Mengetahui Jarak User dari Benda di depannya (optional jg)
C. Bisa Melalui Button Device yg Disetting Dashboard GUardian missal:
- Guardian ada menu buat set shortcut atau quick action untuk user yg dimana isinya missal Auto Accept Call -> volume down button , etc etc
- Buat kalau user udh regis sekali bakal setiap buka hp langsung ke login ke akun User nya dan halamn pertama yg dibuka itu langsung cameranya itu , dan juga bisa ganti tab2 lain kalau perlu dibantu, pokok halaman pertama itu langsung nyala camera dan kedepan karena itu cre feature masalah fitur di tab lain nanti kalau dia ganti sendiri
- Ada settingan di guardian dashboard juga kalau misal User bilang "Call Guardian" maka otomatis akan call langsung
- Nanti diakhir harus di compile dan dicoba sambungin ke IP Adress sendiri yg bisa di setting dan tidak hardcoded

View File

@ -0,0 +1,149 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 6 0 R /F5 8 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
9 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
>>
endobj
12 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260416032159+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260416032159+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
13 0 obj
<<
/Count 4 /Kids [ 4 0 R 7 0 R 9 0 R 10 0 R ] /Type /Pages
>>
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2170
>>
stream
Gb!Sl>>s99'RnB33%s+4G)<FK?f./R$^;Eq\BF4I092e1[uQ`r%LiAdO5rq8M*i]E:1q[V7uh\/3o>.A9YnRV2:m'[E#?Ik3sq3<3!O+2)o;tB#s7$+[$p6h`!og6_6YTLSjdW>rqp@V-:nD11e\'$&Jl"VafL\o<E$,M8pikmEKDdW%`;.dj2CIM=+@\u!jNWLof!h!,9hhG\:+fgkDdPP.r'kTfTc5_"u4..J@N%#3<?(8YlKJ93dWh_!lf.?Q4]'SC8WZD1g=sf=l>TPi["Gd95"u"(W%Un@),IgR'.f48dVh<JA6jake9^Q73NBC$%nX<Z3_LC=o8<Z<s[\Mn*spfrY[rXc1U\^fN[^MZ0-[p]t%.Jk?:)olI^0hMqFfO7I5$`o%9^Rj!O;t^pe,&5,[2r5Q]"!?,!Tb::VB>(40XTClT+9lg6Z^,<;<kU(D8e,*hC5q'\c)<%Rb(d>HL<EET[sBM5ZZlR03nd\[V3lTZL%_tYt!>;U;#PDdu5jVqh`m7__CEYP*NIKE\WgR,IMQ2Aa.BsgOW?14)kU=HES;VS--.G_\M'C%1^p[)-En[uZrLq&csW9Y%V\k\Zdomg40%c-E1meE<?!@kAp_PpagA_Ud1:kk;>[Y&:IGMhQH>a;-hDPOb?7k`(b']np.NA3:W*>@J%-uKmni16$6U#!9D*i4_7MI)m5`6BC+)h;O>Y;2lm,KJ"r=bN8<>>6fP_2G_@N[0DS;GL-[HOb'CZ<f0H.MhO8%)sccML2>:_,r0(cBE@GEKim\E[T9?L_4)f.RjuoAi\/&;huR:.1;A_@lY1>-l3$3$;g=&Q"2S66X7)*pL:W;&\:1>q6r]9a<nYPMI*-2dNK9;j'[4ATR.ZX!n&KdbbIaDj<?9`\=oK!e^I]*pINCgn'0:Z^aMmc>B??o-DK^<a%$ugm1p^+<'.?3$Kq3a[?ZXHmlMNW?T'oKL?8%V/-g$6j'^??Q3D&9YV#TN@g;4llaiUR1a"u:_=rFBAlrdf8p31Qibp,'%$6g0qK_/+C^LeC"R"0<eA__LKnVB:&t/EWLdhp%58f,nE-7HcC_&$LEa;mYO_Z:WkTNa+8giXejN&\$317ujUa9$B0MCNF'PS0?fW7TW6EWnDfM'HfHbM1_n&#B5Ue/!8N,.mkRsc8b^*69:'OttMXdS2;O8"bLqrL;LKV>f1iIS`]^+Ab\%puAhYI+dG]d3)^oEJMXrdej1^>8F57!"g[0#26c!jD8!i:WUGJG3%"6[1+8q#sfVFVN[\Ut3^Cb+4Lq>U*etYF$(2f^V'uH[11pK3=Ee!qgYh@2:>JimuV(^%rKt.3``&l[AgQ&eu9D2t2.3#Lb*=g6j2bZ.*:+67O?S]E]\*jKb6;,+bj2GgN9nX!s0(b4oAn0V)hp_#blGDKMD'i>$$oP9g8rT",WSJOBhof##,#F[#D57rpaMJOnb<o.;";?0d<Q=rEs#SH1%.j"`kN0Uqs,l&>BqFWh"rB/gQ?TH<BO/$JK,o^<4>K4ON)5TnT9LU`@>ES!Q^&0Y#6SlcOCJSc.Vg/P>Ff)kce"BX3nk1kP#d#m#nV1i^$ioJd1!0W8Td/`St111=l-hdJSM,@ngl`1Lj#:dG^$=p9LC578UZ*$r?<QEsu\I+a4.op8H&I4@]AlkE%j_i2_k$GAL%T#"F9iK$5SK,+8DPOb?7k`t8]JT'\g0[*-T+LHD^4bH:^MKj1mk,+L3U+#Z1W%OSm2PDtDkIZ/[\5QgTPLS$+BCheJ`es?Ns#2)[>=Jlj^D($K(;X-EOJhNBPK,%g@S-!h*.F^e4bnri%UGdV@F5[bheU.L<sFOX@4(GMdhF3e8ZZklZ[QLr^+V%$@*d0"p,5n`?=N`i"kY=kXp\O^=J=E66J9r;+Bt&gC</Spuf'8P@nJdon7A_8O9'*H!F2X.ciO:!o9]1nd+0s'h+lV20ZW:;`)4TMb7JK=]Ld+9qV!q;c76Vb'3?fo.*\ck4@D(WJFQX-LIfk>3/Lt=.?AhB:n%uiT4WQSaR&K5+I!]hO]p;%gpcg*QbBZ1/2m1GglVl0CiTBkS\L1Ni'tZ+C%;FqJB@/EBF!&33"WD1/EuV*Z@DkY7?Yl:6VAJ))CGP@]MfN1Z-^,G4T5dcEG,[AArthi?<apo?adeC>l/cM>m[8.aPcSq%oOk6Sn~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1920
>>
stream
Gb"/(>BALX'Ro4HS<rZ*:?#H?/_dTb9+orq-JbQ8REj)3dkO;m\&P;R+Idkq8+Mpa8P$q:Cgk-#P0QMZ4.hQ:!>F?YItoh1;]jl0-U9*T[m2L^!rio0Ick@!PTr1E*65B'38?<W@hP;eh1j2($OiMN(/?f)PB`T_2X-G]4Y;h_!qBkKOZ94HEUKh=1E=Qk6b>]TSKmX=5EsP!ps\u@i#Q(s#H.[;MU<OUIg??WNdmK?s)$`Q(]E4K.(;aj;,_ZtD@ps/Y@bKTXI/Ni,UQ@i1)Rt31(d+aj(R:@oXGc864M[j/Sr@"K2@G"?='VI>t/r=72&pNK'T7^EX9(C(oHfq/L6lG+rRTK1-BG8.+Rg'mW+7!`eZTZ\Y75DEbioIWg*n#IJ\+nZ%JEEM$1ukjZmmp/^L5hQtCJS&T%lX(2E+WSbN'=BlD@RbMM.TCE9Q%5-(s)d:ds%N]9W31fnQT-=9\a([A$I>6Q@0j1nZ`b=O#b?S2pZTC0m<RS;0$dp3_km\)3,'I:L_N+$f=K6\4Y'0$M5!#J_M?2F;#!9.kGs"RYGH))Q-;g9B;U_F[T.<Qa=6]pPF>+3D(BO>=HG8+Gb-0#I.?/Xmma)J"ff`V^"Bu"i,E*STP'WsYC<P[F^UD"c)@rmn9VElfrWk:&kMl6cpm%cVl10Dr$A_MeJZDfY4S9)sB,,&IoFUW/gN8Y9#UJd,Nk!Lpk/[QgAp+V1*V5nD(JiW3q:]4tjAb,n$m^@oef0c[pN]!m8Z&k\cf5jSD[i&TE>5lMB[VN#_Y`%`famVOqbl@J6048"8h.X4P[alh#go@_2I%skHX$'*gZUGL\JW/#f]BY:D/_X7=#NNEL@=1BK`n/h"nVL"He#aMFZk(%K.(@1jf\2tf4a%)r821@TPNLM=&R`%lY%"eAn3c7JTY!E),+#nU=7jd1I1-o).FDPWcS`D[>if%ap"nbecK/$@<6@4s)2t=)k/a.8pr>8FjqpHBX2a:YBpd\!4:_?2FEO9U=q&=:LVA6H=.]`_qZag\qj2`G5<Mj1*E3YF4gTO+S>N9=S*N^42=iXsUp9(W6[kbg)6WA4?^Q9`VWmDV\fG$\,nV^@g<o4r\d*^i,8/>N6tU-QX/lqSRZ9,5k&[U=&;G3DBY`0k6>`>eKaPs'#!#<m;O^rc':9-[1($H51CihA[N2PUbB?@ro18dJAc)KDnCo&L/\nQb]"i?=mHl>'h:&+^KR&mKZ8@?od,fYH,'gm`lJ(!HnBI[?b[W3S7mW/GSh6,<.?G@er+I@okp-Us$`j`Ha:e1@\JS]d<1'79:qqqL'K^@Ra@-SI3Ym)3oIni\$b"W9=*r/D/2';airA;"Xn7X-Jl8?\YQ.46Mj[/(@7OS_0-,J]+QaTCM`CjNS94@"@o@4U1N=A78E9Yp?6\fZ)XP;g<L$?CDtE+dU2aqS(6(P1DMpT26b'6A=uS"8_6#@:[dc:GMcIj,bX2Bbif24sD!VOF$@A?m<*O[6X'2I(j'J@2eH$S3Y=0"paCloT8)&%(4N"T^.!q\Lf'"W4/'N7!`GVX>!3?Ou@u0[FO!r,JUGU'3GQYRA<(=@Y[OaA19sMe0b=J>8Y%Schl8&)-\q"*dR<9lHhf:mF)_+Vr&QdNVj':<2dW0N+P.:FdW8n-DneU8+I8!W3XaLbl7A1/?q>OntJaEb:CEk;^^A?2\@#,HkfLGeL5\0S#+KfOdeFPYN0G@r-8J((8ENuo4CIi;KT,4^'XPe1<`)4@8.o5pY[*!6R@bJ'%RbDYIAjqQX<"rYH"2,7=OkiC=)!.cl:?9<0D0II]nZM/&%JcfBf!BW%G97d^XSa_jki4.%>J&5)#^)d;a2AXPX0rfNNE27P[D).?C+Q%X:GA9Y@XDNfX*0oZC`XipgX+M!_"urRelH_A]a0q?g/A,8rrTCL,+&~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2461
>>
stream
Gb"/h?$"c/&r,lJ.s<tO,<98_r$LWd4)l7P:"l2&`9Qta2\69eN%'o9+ff?)s8BS18Vs`)Il&3R3M)%]#ja0oh$:Qr74*I7r>Ek:h@Af>g`jq=iIic6E,#(dSIKpqorP$gmhe>%H4C+eAN56T@2rVR3#9sg,K\!*TMe1@U,4<FR3"45f+3hUni\hX9d?mio';'Jprp"nmj@k(iE/9B^\JRX&LkugcW?A(IM>q46]D]SB#%Y,6I0;YK;WKfJPYB,>6(@4SHihmJgR[IVA>+CmA*H>C&>o7/Q]X7e%A258Zi`XHG!;'-ObV3CVA0!@sBi#PN=2eLZ]^5ou2Rm/$M/qlo]\T0U6Sq]_O*.H(&BObgTOUK_Aa\I.]QSMBn[O#!X*u+uq3s4C1F]qI-a$`F>>a&Jg1('n*/jF<l7iUZ!"-^4P^8L!5lT.:OdLjGHUXG/"UM<>#V<lK;r/o<W+1[dno@.S3P^:XX>AJD.2o[p50N>'r-YQm>hiFF$[=kfq/\\h:$\92R-G`G!WDnnfGOeh7E-A1bJqell`dr!MJA>/dtZ(Uiq!h?]$'i;].8_<eu1/2ETH*G&aab]H1@56'EU?7[2O4!VoS4<NEq)Rerc34s)^f7Ic_7Vq^=%7`AEg>/Ok)!G8\*[mM]FX3dJ&K&6[*7,4(#QhtCX_[2H/[-I7IP'\7ee;kO)UTATT`kb]3iHuai.l1tl?@UA24%DDIX#aoAa(''PiiS&dtO%U`0"mAFNW'3H%MOm3r/QB7Y>otPbYor[dY2kI+.t!&2?t583\"(1"D!2/Vo#J.6n$0)\A_5*$Fgc>p/_9IfZ9S?K@rYI_hdP4r$RD.>pRd@Dpg%54OM0lSY4d&=>e&hr#lN#T:mU#d-!3GdY[<ciR>-.86i,GTG:]aV1Z8(a."85_##T;=LkXD"lK37u)k6*q==VL)_q3R9T=LO7r9]-?q)al=_dACEK(9j^munQV!eAO/W(Zn.H6++.6JnXT*.0'K;0]S.G<1Q/ls_S`[2Mj(#Ah;q%kW2s-9I%/s/dffKe>CHZbJ@-K[d4MG]pW(._H?=3d<!q22"C8kf=b'a0h;Y5mpSfr/4Z@V+@O.;L2P0X&e7%raneMu_`$?41Yd[\AVXmXMh4m$"e*cnfp#>`qnd<<Gk"FGqdKfs$kZ%oRM@]rd](\Vran&1g<]XtH35mQ.P:X*4JdN'jFR9=2W(:%Oa]*n\Nfpr>WmZmm6qF?9Q,OTD29c.9oi3(mVQYE1])S6euVo/9/q_*6,dCRnDgKgqBVS0%\19G^*YcB%(01qg-3^FJAe(+DAGC_4:I<bLR`k-[VGk]^BG[?;Pc[I"nlhr]XB)NUu_6dG36E"H#L?E"0%a_/bHRGiFrR'G@:&&khFiHWCBWqN9a)."ThKC@jR"f>.+bP[6k(l2J[=.M,$qGEOAZ"nc3R?kM2poJcUh@^de""E'#:k?mhf,q)h_<<[l]Xn73NUOPbAF@;=f>S?=h>AC"`2JYP5-*)7ij(7A*9!`Aom=n16Jn(_pFlUmF,<N$TuoNK+S=$QU#24=`)K#W`%2I<(4'jP%b-E:@aP`a>HQe?&16J=X<]T]T@g,.aCJJojJ8^@Gjc'3f>jdA0oZ\n_@eIn8+lQ$b"Cj:X^E5G-j@7c(;&bloA?.,G_naDUmWuAf[1!aln%4aOp%adsg8noC67.JJo,GqV+-!2&=aqrbI**:LqdAK>B34*/fbTm\C5GQ328'/nY6dKXsLb6Th9Rbf`YJU$*1.,6)a3-L7m+8iC!T8L@7cj((b+jCaW_$Hc#,8++tX/AM<B`/&eDD=eT'dE33P7[;nk3RZEjrCo&:WOc75<'kf#_iPZE4I-'ZoC(Xgc!EC!N#_V?FtqL3qDJKUeApi11-d,d;*LU*Z6[kP*CVcUAKmtUO@oU:W1]?tgn6Qp.8h4\hW.f[gDp@n1)tb]P3P[f<CEj\FeV,506,oOlD-qTV-[EX]L1=qR&EItin#s+l:j]WPfn@ab/^<H+dIlR^sET.8B"F](3ud]nH+G8K:u5=g^48?G@//T&26*#;r$[:.TZ1*6bY[HcV8uQoVYAfW>&X'!7c7gW]HfL5i!qceF'+_"SDhE2X/",?^0'3kD$-h=u#>:?QJCS=h^/JW-UbqWM-P^V\>q`f2pLs[0!S*V&7[kba4T0U;0%]!/X8b+/n1277^KdYjbaGfH.[%7`BQ=m1=BC\tAoGa0sg90:f2+D&MgGG<Z$)G<R.mGMVb4msP,5"J$`aWr&uF/7AB;Dt.+?;d)C(9&k-o#IfH9Y5&d@po`Mb`CJtYUh*>bJXn7*!A[d<ht(iNDN_5fU=d0?rt`>i)]SE-;ld#[c0$L)<+a'Ie6)6u<#%9e&,_;,2Mk;*PH^k4BolM<`BbQc`7H?F<(9TIaTL'`h;%'>j/SrmXqHrhCqAP'Vue8<Yq-RArlI\u[Ch@<:HRCfniP5h^&J2=OE:g~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1182
>>
stream
Gatn%_/e9g&;KY"MS5g.*Ld'p![)i23cUT^Vdn?FjO+E(5pB5=?jT`9hnBIjU_m<NSVa0\JH18?0)#/YU-S3@LYng1GalU?CC6:l^':#o$08k;]6s.pK'm9/30H&W1!#_NYp:%8iTjQ<]r<Q)C2#N3]',e@<O8pIWYuKn,=r'ZN]D1a`Lb^>/.u[@_D;4a7PPWlGiDO.-4H..koB'LQ+!_@gI.GP1//n6\4B&ID*k39cu'$ud-.k"-$EH$lgk8t%-*R,BJK_8SqN%i/Dc);;sAC71kWFFb/8fif*j&:"sPN7:U4+R&kR[.E->6[Q*..hIeX6?c]Fd:fe9@CH4P31^=0J3LN3>ur6B\GZ\\436Fq4@O+(_)`djJqQ88qq+3q^CQj4^Gi=4Y8h0&ZggC>.RG09Rfcs((-eQ'm]@oTp\D2k=)..sT!Q/-t*)@q<Ph!2%V_n]L>\g`M7fp0r+$<K/UhnuboQ.=_YgP=`@AVKrk2LX3A(09-g>MXU%i+o\t:1fC7H%.h8!;aFWD5D*CUF-:fqriP5!gGE%+N_R?jm=dA77t91gdDS/%a?&"HmXrn,Ie&*$#gP4!uF&V%do.r8LTWCI=QU(b9'oUbH@l<!F'2q>.PK3fkSU!1G@',,=gGcdg,e7l/eHdrmi+b^'PVYAoWY]9pd)eF&jsP_=^VQ42nGj/$ji%<;8'WM=aP/3I:e0"m`a7>noG0pmjmhJOL`Gj"*q=`!KPD$(T:KI;TseUKP5bGV"$ZR"rsq\CO_3h4@<q_2GL3rH%RS]_$SR_ah+7Yh%NQ.(.bm!3/1mVhImHqI4IR)H@7B)cQU0lH5_p]/qI3I(-sq3[fAhp'PFuf8PatBC)#n-T&_&#F3P*Q=?=10hk,kUJ]/o:;WLg&j&1/7i<(n7#&!$S8=E^_UEV9BUXEcD+`fHiS$8=E%U40Yhe$#dO7O4:+R]br1K*\Jcf:@Rn=-Bm&N`7b;Ms4B/21Q=2W_[DPa,S*aZarqkh?%@$\3V`AEnE=i+^d0fjnA&FpDt%0(ce&o6L`id6I#hH+l<Lbm1B!"i"m?kjtZYl)QRdMW@G8&%!)o?`c$k1=RSI!9`)@:o!nUs-1FOYW9VMeQ_Jqi;eIc1@EDX4Fs)b>=tN3sud\\CBG:*I->;2Vc(hgj0fD.P-V$UJZ3`X3HhI'Gg]Mh9^4+~>endstream
endobj
xref
0 18
0000000000 65535 f
0000000061 00000 n
0000000132 00000 n
0000000239 00000 n
0000000351 00000 n
0000000556 00000 n
0000000639 00000 n
0000000716 00000 n
0000000921 00000 n
0000001026 00000 n
0000001231 00000 n
0000001437 00000 n
0000001507 00000 n
0000001788 00000 n
0000001867 00000 n
0000004129 00000 n
0000006141 00000 n
0000008694 00000 n
trailer
<<
/ID
[<6a9991eb3c6f00514ee41f199a9cc314><6a9991eb3c6f00514ee41f199a9cc314>]
% ReportLab generated PDF document -- digest (opensource)
/Info 12 0 R
/Root 11 0 R
/Size 18
>>
startxref
9968
%%EOF

BIN
assets/gambar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

2
walkguide-backend/demo/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
walkguide-backend/demo/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip

295
walkguide-backend/demo/mvnw vendored Normal file
View File

@ -0,0 +1,295 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

189
walkguide-backend/demo/mvnw.cmd vendored Normal file
View File

@ -0,0 +1,189 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

View File

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.walkguide</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name/>
<description/>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-h2console</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.walkguide;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

View File

@ -0,0 +1,48 @@
package com.walkguide.config;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.walkguide.entity.User;
import com.walkguide.repository.UserRepository;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
public class DataSeeder implements CommandLineRunner {
private final UserRepository userRepository;
@Override
public void run(String... args) throws Exception {
// Mesin enkripsi password
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// Ngecek kalau tabel users kosong, baru diisi
if (userRepository.count() == 0) {
// 1. Bikin akun Guardian (Pendamping)
User guardian = User.builder()
.email("guardian@walkguide.com")
.password(passwordEncoder.encode("guardian123")) // Password barunya
.role("ROLE_GUARDIAN") // Role udah diganti
.build();
// 2. Bikin akun User (Tunanetra)
User user = User.builder()
.email("user@walkguide.com")
.password(passwordEncoder.encode("user123"))
.role("ROLE_USER")
.build();
// Simpan ke database
userRepository.save(guardian);
userRepository.save(user);
System.out.println("✅ Data Seeder berhasil: Akun Guardian dan User telah masuk ke Database!");
}
}
}

View File

@ -0,0 +1,19 @@
package com.walkguide.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Walk Guide API")
.version("1.0")
.description("API Documentation for Walk Guide Application (Final Exam)"));
}
}

View File

@ -0,0 +1,47 @@
package com.walkguide.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// MESIN HASHING PASSWORD: Biar password yang disimpan di database gak bisa dibaca langsung, kita enkripsi dulu pake BCrypt
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Aturan jalan masuk ke API kita:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(request -> {
var corsConfiguration = new org.springframework.web.cors.CorsConfiguration();
// Izinkan semua origin localhost dan semua port (biar Chrome gak ngeblok)
corsConfiguration.setAllowedOriginPatterns(java.util.List.of(
"http://localhost:*",
"http://127.0.0.1:*"
));
corsConfiguration.setAllowedMethods(java.util.List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(java.util.List.of("*"));
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}

View File

@ -0,0 +1,35 @@
package com.walkguide.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.walkguide.dto.AuthRequest;
import com.walkguide.dto.AuthResponse;
import com.walkguide.service.AuthService;
import lombok.RequiredArgsConstructor;
@RestController
@RequestMapping("/api/auth") // Ini URL awalnya
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
// Ini URL lengkapnya jadi POST http://localhost:8080/api/auth/login
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody AuthRequest request) {
try {
// Lempar kardus AuthRequest ke Service
AuthResponse response = authService.login(request);
// Kalau sukses, kirim status 200 OK + isinya
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
// Kalau gagal (password salah/email gak ada), kirim status 401 Unauthorized
return ResponseEntity.status(401).body(e.getMessage());
}
}
}

View File

@ -0,0 +1,43 @@
package com.walkguide.dto;
import java.time.Instant;
public class ApiResponse<T> {
private boolean success;
private T data;
private String message;
private String errorCode;
private String timestamp;
// Constructor buat Sukses
public ApiResponse(boolean success, T data, String message) {
this.success = success;
this.data = data;
this.message = message;
this.timestamp = Instant.now().toString();
}
// Constructor buat Error
public ApiResponse(boolean success, String errorCode, String message) {
this.success = success;
this.errorCode = errorCode;
this.message = message;
this.timestamp = Instant.now().toString();
}
// Getter Setter manual
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getErrorCode() { return errorCode; }
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
public String getTimestamp() { return timestamp; }
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
}

View File

@ -0,0 +1,19 @@
package com.walkguide.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public class AuthRequest {
@NotBlank(message = "Email tidak boleh kosong")
@Email(message = "Format email tidak valid")
private String email;
@NotBlank(message = "Password tidak boleh kosong")
private String password;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}

View File

@ -0,0 +1,28 @@
package com.walkguide.dto;
public class AuthResponse {
private String token;
private String role;
public AuthResponse(String token, String role) {
this.token = token;
this.role = role;
}
// Manual Getter Setter (Gantiin fungsi @Data)
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}

View File

@ -0,0 +1,37 @@
package com.walkguide.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "users")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
// Ini otomatis bikin ID angka urut (1, 2, 3, dst)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Email gak boleh kosong dan gak boleh ada yang kembar
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
// Buat nandain dia ini ROLE_USER atau ROLE_ADMIN
@Column(nullable = false)
private String role;
}

View File

@ -0,0 +1,27 @@
package com.walkguide.exception;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import com.walkguide.dto.ApiResponse;
@ControllerAdvice
public class GlobalExceptionHandler {
// Nangkep error kalau login gagal / user gak ketemu
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ApiResponse<Object>> handleRuntimeException(RuntimeException ex) {
ApiResponse<Object> response = new ApiResponse<>(false, "AUTH_ERROR", ex.getMessage());
return ResponseEntity.status(401).body(response);
}
// Nangkep error dari Validasi (misal email format salah)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Object>> handleValidationException(MethodArgumentNotValidException ex) {
String errorMsg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
ApiResponse<Object> response = new ApiResponse<>(false, "VALIDATION_ERROR", errorMsg);
return ResponseEntity.status(400).body(response);
}
}

View File

@ -0,0 +1,16 @@
package com.walkguide.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.walkguide.entity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Ini fungsi ajaib Spring Boot. Cuma dari nama fungsinya aja,
// dia udah tau maksudnya "Tolong cariin user di database yang emailnya = X"
Optional<User> findByEmail(String email);
}

View File

@ -0,0 +1,34 @@
package com.walkguide.security;
import java.security.Key;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
public String generateToken(String email, String role) {
return Jwts.builder()
.setSubject(email)
.claim("role", role)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
}

View File

@ -0,0 +1,36 @@
package com.walkguide.service;
import com.walkguide.dto.AuthRequest;
import com.walkguide.dto.AuthResponse;
import com.walkguide.entity.User;
import com.walkguide.repository.UserRepository;
import com.walkguide.security.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
public AuthResponse login(AuthRequest request) {
// 1. Cari user di database kampus lu berdasarkan email
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("Email tidak ditemukan!"));
// 2. Cocokin password yang diketik sama password yang di-hash di database
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new RuntimeException("Password salah!");
}
// 3. Kalau email dan password bener, suruh JwtUtil cetak Token
String token = jwtUtil.generateToken(user.getEmail(), user.getRole());
// 4. Bungkus token dan rolenya buat dikirim balik ke Flutter
return new AuthResponse(token, user.getRole());
}
}

View File

@ -0,0 +1,17 @@
# Server Port
server.port=8080
# === KONEKSI H2 VIRTUAL (GAK PAKE PASSWORD) ===
spring.datasource.url=jdbc:h2:mem:walkguidedb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Biar Spring Boot otomatis bikin tabel
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# JWT Configuration
jwt.secret=WalkGuideSecretKeyYangSangatPanjangMinimal256BitUntukKeamananWalkGuide
jwt.expiration=86400000

View File

@ -0,0 +1,13 @@
package com.walkguide;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}

View File

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

View File

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "05db9689081f091050f01aed79f04dce0c750154"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: android
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: ios
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: linux
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: macos
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: web
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: windows
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -0,0 +1,16 @@
# walkguide_app
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

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

View File

@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.walkguide_app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.walkguide_app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

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

View File

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="walkguide_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 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.example.walkguideApp;
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.example.walkguideApp.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.example.walkguideApp.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.example.walkguideApp.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.example.walkguideApp;
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.example.walkguideApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

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

View File

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

View File

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

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Walkguide App</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>walkguide_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

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

View File

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

View File

@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; // Wajib import ini
class ApiService {
static String get baseUrl {
if (kIsWeb) {
// Jika di Chrome/Web, tembak localhost langsung
return 'http://localhost:8080/api';
} else {
// Jika di Emulator Android, tembak IP khusus 10.0.2.2
return 'http://10.0.2.2:8080/api';
}
}
final Dio _dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 5),
// Penting buat Web agar tidak kena error CORS di sisi Client
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
Future<Response> post(String path, Map<String, dynamic> data) async {
return await _dio.post(path, data: data);
}
}

View File

@ -0,0 +1,8 @@
abstract class Failure {
final String message;
Failure(this.message);
}
class ServerFailure extends Failure {
ServerFailure(super.message);
}

View File

@ -0,0 +1,17 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorage {
final _storage = const FlutterSecureStorage();
Future<void> saveToken(String token) async {
await _storage.write(key: 'jwt_token', value: token);
}
Future<String?> getToken() async {
return await _storage.read(key: 'jwt_token');
}
Future<void> deleteToken() async {
await _storage.delete(key: 'jwt_token');
}
}

View File

@ -0,0 +1,13 @@
import '../domain/user_entity.dart';
class AuthModel extends UserEntity {
AuthModel({required super.token, required super.role});
factory AuthModel.fromJson(Map<String, dynamic> json) {
// Ngambil dari json['data'] karena API Spring Boot lu sekarang pake ApiResponse
return AuthModel(
token: json['data']['token'],
role: json['data']['role'],
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:dio/dio.dart';
import '../../../../core/api_service.dart';
import 'auth_model.dart';
abstract class AuthRemoteDataSource {
Future<AuthModel> login(String email, String password);
}
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final ApiService apiService;
AuthRemoteDataSourceImpl(this.apiService);
@override
Future<AuthModel> login(String email, String password) async {
try {
final response = await apiService.post('/auth/login', {
'email': email,
'password': password,
});
// Karena API response lu ada "success": true
if (response.statusCode == 200 && response.data['success'] == true) {
return AuthModel.fromJson(response.data);
} else {
throw Exception(response.data['message'] ?? 'Gagal login');
}
} on DioException catch (e) {
// Nangkep error dari backend (misal status 400/401)
throw Exception(e.response?.data['message'] ?? 'Terjadi kesalahan jaringan');
}
}
}

View File

@ -0,0 +1,30 @@
import 'package:dartz/dartz.dart';
import '../../../../core/failure.dart';
import '../../../../core/secure_storage.dart';
import '../domain/auth_repository.dart';
import '../domain/user_entity.dart';
import 'auth_remote_data_source.dart';
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
final SecureStorage secureStorage;
AuthRepositoryImpl(this.remoteDataSource, this.secureStorage);
@override
Future<Either<Failure, UserEntity>> login(String email, String password) async {
try {
// 1. Suruh data source nembak API
final userModel = await remoteDataSource.login(email, password);
// 2. Simpan token langsung ke HP biar UI gausah ribet
await secureStorage.saveToken(userModel.token);
// 3. Balikin data sukses (Right)
return Right(userModel);
} catch (e) {
// 4. Balikin pesan error (Left)
return Left(ServerFailure(e.toString().replaceAll('Exception: ', '')));
}
}
}

View File

@ -0,0 +1,8 @@
import 'package:dartz/dartz.dart';
import '../../../../core/failure.dart';
import 'user_entity.dart';
abstract class AuthRepository {
// Pake Either: Kiri balikin Failure, Kanan balikin UserEntity
Future<Either<Failure, UserEntity>> login(String email, String password);
}

View File

@ -0,0 +1,6 @@
class UserEntity {
final String token;
final String role;
UserEntity({required this.token, required this.role});
}

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
final String role;
const HomeScreen({super.key, required this.role});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dashboard Walk Guide')),
body: Center(
child: Text(
role == 'ROLE_ADMIN' ? 'Selamat Datang Admin!' : 'Mode Walk Guide Siap!',
style: const TextStyle(fontSize: 24),
),
),
);
}
}

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