diff --git a/.gitignore b/.gitignore
index 24476c5..29a3a50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,7 +27,6 @@ migrate_working_dir/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
-.packages
.pub-cache/
.pub/
/build/
diff --git a/.metadata b/.metadata
index 4fd81af..c2aa44b 100644
--- a/.metadata
+++ b/.metadata
@@ -1,11 +1,11 @@
# 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.
+# This file should be version controlled and should not be manually edited.
version:
- revision: 4b12645012342076800eb701bcdfe18f87da21cf
- channel: stable
+ revision: "603104015dd692ea3403755b55d07813d5cf8965"
+ channel: "stable"
project_type: app
@@ -13,11 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: 4b12645012342076800eb701bcdfe18f87da21cf
- base_revision: 4b12645012342076800eb701bcdfe18f87da21cf
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ - platform: android
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ - platform: ios
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ - platform: linux
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ - platform: macos
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
- platform: web
- create_revision: 4b12645012342076800eb701bcdfe18f87da21cf
- base_revision: 4b12645012342076800eb701bcdfe18f87da21cf
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ - platform: windows
+ create_revision: 603104015dd692ea3403755b55d07813d5cf8965
+ base_revision: 603104015dd692ea3403755b55d07813d5cf8965
# User provided section
diff --git a/README.md b/README.md
index 55e2290..bf1e030 100644
--- a/README.md
+++ b/README.md
@@ -1,86 +1,63 @@
-# Flutter on Codespaces
+
+# ๐ฒ GolekMakanRek-Mobile! ๐
+**GolekMakanRek!** adalah aplikasi untuk Anda para penduduk dan juga turis di Surabaya untuk memilih kuliner sesuai selera.
+
+## ๐ Back Story
+Surabaya, sebagai salah satu kota besar di Indonesia, memiliki kekayaan kuliner yang sangat beragam, mulai dari jajanan kaki lima hingga restoran mewah. Namun, dengan begitu banyak pilihan, baik penduduk lokal maupun wisatawan sering kali kebingungan menentukan tempat makan yang sesuai dengan selera dan kebutuhan mereka. Dari sinilah ide GolekMakanRek! munculโsebuah platform yang dirancang untuk membantu masyarakat Surabaya dan para wisatawan menjelajahi serta menemukan kuliner terbaik di kota ini dengan mudah. GolekMakanRek! bertujuan menjadi solusi bagi setiap orang yang ingin menikmati hidangan lezat, tanpa harus repot memilih di tengah keramaian kota.
+
+## ๐ฅ Anggota Kelompok
+| Nama | NPM | Akun GitHub |
+| -- | -- | -- |
+| Nisrina Annaisha Sarnadi | 2306275960 | [aisss](https://github.com/nsrnannaisha) |
+| Kaindra Rizq Sachio | 2306274964 | [kaindraa](https://github.com/kaindraa) |
+| Muhammad Afwan Hafizh | 2306208855 | [mir4na](https://github.com/mir4na) |
+| Joshua Montolalu | 2306275746 | [HamletJr](https://github.com/HamletJr) |
+| Ignasius Bramantya Widiprasetya | 2306245604 | [BramantyaWidiprasetya ](https://github.com/BramantyaWidiprasetya) |
+| Muhammad Falah Marzuq | 2306202315 | [falahMarzuq](https://github.com/falahMarzuq)
+
+## ๐๏ธ Deskripsi Aplikasi
+**GolekMakanRek!** adalah sebuah aplikasi yang memberikan kemudahan bagi penduduk lokal maupun wisatawan untuk menjelajahi berbagai pilihan kuliner di Surabaya. Dengan desain yang sederhana tetapi intuitif, platform ini memungkinkan pengguna mencari restoran dan makanan sesuai selera mereka. Melalui fitur pencarian yang dapat difilter berdasarkan jenis makanan, lokasi, atau popularitas, pengguna dapat menemukan rekomendasi kuliner mulai dari makanan kaki lima hingga restoran berbintang dengan cepat dan mudah.
+
+Selain melihat deskripsi restoran dan menu yang tersedia, pengguna juga dapat membaca ulasan dan melihat rating dari pengguna lain. Fitur ini sangat berguna untuk membantu dalam memilih tempat makan terbaik berdasarkan pengalaman orang lain. Uniknya, pengguna juga dapat berkontribusi dengan memberikan rating dan ulasan sendiri setelah mencicipi makanan dari restoran yang mereka kunjungi. Rating ini kemudian akan terakumulasi, memberikan gambaran yang lebih akurat tentang kualitas makanan dan layanan di setiap restoran yang terdaftar di GolekMakanRek!.
+
+Pengalaman pengguna semakin dipersonalisasi dengan adanya fitur wishlist, yang artinya pengguna dapat menyimpan daftar makanan yang ingin dicoba di kemudian hari. Ini membuat GolekMakanRek! tidak hanya sekadar direktori makanan, tetapi juga tempat bagi komunitas kuliner untuk berbagi pengalaman, memberi rekomendasi, dan membantu orang lain menemukan tempat makan terbaik di Surabaya.
+## ๐ Daftar Modul
+Berikut adalah daftar modul yang akan di-implementasikan:
+
+| Modul | Pengembang | Penjelasan |
+| -- | -- | -- |
+| **Autentikasi & Admin** | All | **Autentikasi:** Berperan mengatur Registrasi dan Login akun pengguna dan admin.
**Admin:** Berperan dalam mengelola konten aplikasi. Admin memiliki hak untuk menambahkan, menghapus, dan mengubah data restoran atau makanan. Selain itu, Admin juga dapat mengawasi dan memoderasi ulasan pengguna. |
+| **User Dashboard** | Bram | Berisikan informasi pengguna seperti nama, umur, nomor handphone, dan alamat. Pengguna juga dapat mengedit informasi pribadinya. |
+| **Homepage: Search, Filter, Like** | Joshua | Pada homepage, pengguna dapat melihat dan mencari dari data-data yang tersedia pada aplikasi. Pengguna dapat memilih untuk mencari dari daftar restoran ataupun daftar makanan. Selain itu, pengguna dapat melakukan reaction yaitu menyukai makanan yang ditampilkan pada Homepage. Nantinya, angka dari *like* tersebut akan dijumlahkan dari semua user yang menyukai makanan tersebut. |
+| **Restaurant Preview & Follow** | Ais | Fitur ini menampilkan restoran-restoran beserta deskripsinya. Ditampilkan pula daftar menu yang tersedia. Pengguna dapat memberikan rating yang hasilnya akan terakumulasi sebagai rating restoran dan melakukan _follow-unfollow_ restoran. |
+| **Food Preview** | Hafizh | Pada fitur Food Preview, pengguna dapat memberikan ulasan dan rating mengenai produk makanan yang ada pada setiap restoran. Setiap ulasan yang diberikan akan ditampikan ketika pengguna melakukan klik pada button terkait โulasan produkโ. Selain itu, terdapat penghitungan rating yang memungkinkan hasil rata-rata dari setiap rating yang diberikan pengguna akan ditampilkan pada masing-masing produk makanan. |
+| **Wishlist** | Falah | Pengguna dapat menambahkan suatu makanan ke dalam daftar berupa wishlist. Daftar ini berisikan makanan-makanan yang diinginkan pengguna. Pengguna dapat melihat daftar tersebut dan mengedit daftarnya seperti menambahkan makanan lainnya dan juga menghapus suatu makanan dari wishlist. |
+| **Forum Kuliner** | Kaindra | Antar para pengguna dapat melakukan komunikasi dalam bentuk forum yang dibuat. Sebagai contoh, Pengguna A membuka topik pembicaraan di forum. Nantinya, Pengguna B atau Pengguna lainnya dapat ikut mengikuti forum tersebut dengan melakukan reply. |
+
+## ๐คบ *Role*/Peran Pengguna
+### 1. ๐จ๐ปโ๐ป Pengguna
+#### a. ๐ Pengguna (terautentikasi)
+Pengguna yang sudah melakukan register dan login dapat:
+- Melakukan pencarian dan filtering daftar makanan dan restoran.
+- Membuka fitur food preview dan restaurant preview.
+- Membuka dan mengubah informasi pengguna pada user dashboard.
+- Membuka dan menambahkan wishlist pribadi pengguna.
+#### b. ๐ Pengguna (belum terautentikasi)
+Pengguna yang belum melakukan register dan login hanya dapat:
+- Membuka homepage.
+- Melakukan pencarian dan filtering daftar makanan dan restoran.
+- Membuka fitur food preview dan restaurant preview.
+
+## Alur Integrasi
+Alur integrasi aplikasi Flutter ke proyek web kami adalah sebagai berikut:
+1. Aplikasi Flutter akan menggunakan library `http` untuk melakukan *request* dan *response* HTTP kepada server Django, khususnya kepada endpoint yang mengembalikan data dalam bentuk JSON.
+2. Aplikasi Flutter juga akan menggunakan library `pbp_django_auth` untuk memfasilitasi proses otentikasi (login, logout, register) dan menyimpan *session* lewat *cookie*.
+3. Untuk setiap model yang digunakan dalam aplikasi Django, akan dibuatkan model yang bersesuaian pada aplikasi Flutter untuk melakukan serialisasi dan deserialisasi data JSON ketika mengirim dan menerima data dari server Django.
+4. Untuk menerima request GET dan POST dari Flutter, akan dibuatkan endpoint (jika diperlukan) yang dapat mengolah request berisi JSON (POST) dari Flutter. Endpoint ini juga dapat mengembalikan response JSON (GET & POST) yang akan di-*parse* dan diolah oleh aplikasi Flutter.
+
+## *Dataset* yang Digunakan
+Dataset yang digunakan berasal dari [Kaggle - Indonesia food delivery Gofood product list](https://www.kaggle.com/datasets/ariqsyahalam/indonesia-food-delivery-gofood-product-list).
+
+## Berita Acara Kelompok F10
+Berita acara kelompok F10 dapat diakses di [link berikut](https://docs.google.com/spreadsheets/d/1wk12z7HfZcbrUoaX8TTx7DbVCwNlyiNLAyX6wdyXSx8/edit?gid=0#gid=0)
-This is a template repository for developing with [Flutter](https://flutter.dev/) on the web on [GitHub Codespaces](https://github.com/features/codespaces).
-
-Flutter is a cross-platform UI framework by Google for building apps. Codespaces is a cloud-based development environment that lets you run a full-featured IDE in the cloud. This template repository lets you get started with Flutter on Codespaces in just a few clicks.
-
-**Table of Contents**
-- [Important things to note](#important-things-to-note)
-- [Setup](#setup)
- - [Getting started](#getting-started)
- - [Using a sample](#using-a-sample)
-- [Development Environment](#development-environment)
- - [Developing in the browser](#developing-in-the-browser)
- - [Developing in the desktop app](#developing-in-the-desktop-app)
-- [Flutter Development](#flutter-development)
- - [Developing for mobile](#developing-for-mobile)
-- [Codespaces Usage](#codespaces-usage)
- - [Managing your codespace](#managing-your-codespace)
-
-This repository is generated from the [dilanx/flutter-codespaces](https://github.com/dilanx/flutter-codespaces) repository.
-
-## Important things to note
-
-Codespaces is not completely free. Free users have 120 core-hours per month and Pro users have 180 core-hours per month. The default codespace runs on a 2-core machine, so that's 60 hours (or 90 hours) of free usage per month before getting charged. Make sure to stop your codespace when you're not using it (it automatically stops after 30 minutes of inactivity by default). See more pricing details [here](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces), and manage your active codespaces [here](https://github.com/codespaces).
-
-## Setup
-
-### Getting started
-
-1. Press "Use this template" towards the top right of this repository and create a new repository from this template.
-
- > There's also an option to open this repository in Codespaces and publish it to GitHub later from there, but I recommend creating your own repository first.
-
-2. In your new repository, press "Code", select "Codespaces", then press "Create codespace on main". A container with everything you need to get started will be created automatically, then you'll be taken to your new codespace (VS Code in your browser). If you'd prefer to work on your codespace using the VS Code desktop app instead of the browser app, you can follow these instructions.
-
-3. Press the "Extensions" icon in the left sidebar. You'll see that the Flutter and Dart extensions are already being installed. The environment won't work properly until the installation is complete, so wait for it to finish.
-
-4. In your integrated terminal (the TERMINAL tab), run `flutter pub get` to install the missing Flutter dependencies.
-
-5. In the ports view (the PORTS tab), port 3000 should be listed there already. Right click on it, and, under "Port Visibility", select "Public". This is important so the app can access services on your client from other server ports without getting blocked due to CORS.
-
-6. Run `./run.sh` in the terminal to start the app. A notification will appear saying that an app opened on port 3000. You can press "Open in Browser" to open it, but it won't load until the terminal shows that it's ready. You can refresh once the app is loaded (as indicated by a prompt to press "R" to reload).
-
- > You can find the link to access the app in your browser at any time by going to the ports view, right clicking on port 3000, and pressing "Open in Browser".
-
-7. That's it! Make changes in `lib/main.dart`, press "R" in the terminal, then refresh the page to see your changes appear quickly.
-
-### Using a sample
-
-There are a collection of sample Flutter apps you can use. They're stored in the `samples` directory. If you want to use one of them, find the folder of the sample you want to use, then move the contents of the folder into the root of your repository. At minimum, this should overwrite `pubspec.yaml` and `lib`.
-
-## Development Environment
-
-### Developing in the browser
-
-The browser-based VS Code is the default editor for Codespaces, and has most of the features you'd need. Opening your codespace from [github.com/codespaces](https://github.com/codespaces) will automatically open the browser-based editor.
-
-### Developing in the desktop app
-
-If you'd prefer to use the desktop app version of VS Code, you can follow these instructions:
-
-1. Download the [VS Code desktop app](https://code.visualstudio.com/). You probably already have it if you chose to follow these instructions.
-
-2. Install the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces).
-
-3. Open the command pallette from the View menu (or cmd+shift+P / ctrl+shift+P) and run "Codespaces: Connect to Codespace...".
-
- > Alternatively, click the button in the very bottom left of VS Code (it says "Open a Remote Window" if you hover over it) and press "Connect to Codespace...".
-
-4. Log in if necessary, then select your codespace from the list.
-
-## Flutter Development
-
-### Developing for mobile
-
-Running Flutter in Codespaces makes it a bit difficult to run the app in a mobile simulator. However, developing for the web is basically the same as developing for mobile. I'd recommend opening your browser's developer tools and selecting a mobile device to emulate.
-
-If you're using Chrome or another Chromium-based browser, you can open DevTools like [this](https://developer.chrome.com/docs/devtools/open/) and emulate a device like [this](https://developer.chrome.com/docs/devtools/device-mode/). It'll be pretty similar for other browsers like Safari and Firefox.
-
-## Codespaces Usage
-
-### Managing your codespace
-
-When you're not using your codespace, deactivate it by going to [Codespaces](https://github.com/codespaces), pressing the 3 dots on the right side of the codespace, and pressing "Stop codespace". You can also deactivate it within the codespace by pressing "Codespaces" at the bottom left of VS Code and selecting "Stop Current Codespace".
-
-Edited files in your workspace are not deleted when stopping the codespace and the container won't need to be rebuilt when you start it again. Provided that you're under the storage limit (15 GB for Free users and 20 GB for Pro), you won't be charged if your codespace is offline. I recommend committing your repository changes on your codespace often to avoid losing work if you were to delete your codespace.
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 61b6c4d..0d29021 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -13,8 +13,7 @@ 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-lang.github.io/linter/lints/index.html.
+ # 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
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..55afd91
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 0000000..2fcb0f3
--- /dev/null
+++ b/android/app/build.gradle
@@ -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.golekmakanrek_mobile"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.example.golekmakanrek_mobile"
+ // 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.debug
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6ba622d
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/example/golekmakanrek_mobile/MainActivity.kt b/android/app/src/main/kotlin/com/example/golekmakanrek_mobile/MainActivity.kt
new file mode 100644
index 0000000..2e04c73
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/golekmakanrek_mobile/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.golekmakanrek_mobile
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..d2ffbff
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,18 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = "../build"
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..2597170
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7bb2df6
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -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.3-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..b9e43bd
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,25 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return 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.1.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.8.22" apply false
+}
+
+include ":app"
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..7a7f987
--- /dev/null
+++ b/ios/.gitignore
@@ -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
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..7c56964
--- /dev/null
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
+
+
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..0fcee53
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -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 = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 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 = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 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 = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* 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 = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 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 = "";
+ };
+/* 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 = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* 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 = 12.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.golekmakanrekMobile;
+ 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.golekmakanrekMobile.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.golekmakanrekMobile.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.golekmakanrekMobile.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 = 12.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 = 12.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.golekmakanrekMobile;
+ 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.golekmakanrekMobile;
+ 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 */;
+}
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..8e3ca5d
--- /dev/null
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..6266644
--- /dev/null
+++ b/ios/Runner/AppDelegate.swift
@@ -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)
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -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"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..7353c41
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..797d452
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..6ed2d93
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cd7b00
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..fe73094
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..321773c
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..797d452
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..502f463
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..0ec3034
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..0ec3034
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..e9f5fea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..84ac32a
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..8953cba
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..0467bf1
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -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"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -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.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 0000000..9a40919
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Golekmakanrek Mobile
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ golekmakanrek_mobile
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000..86a7c3b
--- /dev/null
+++ b/ios/RunnerTests/RunnerTests.swift
@@ -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.
+ }
+
+}
diff --git a/lib/main.dart b/lib/main.dart
index 008fa38..516465c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:pbp_django_auth/pbp_django_auth.dart';
+import 'package:provider/provider.dart';
+import 'package:golekmakanrek_mobile/screens/login.dart';
void main() {
runApp(const MyApp());
@@ -7,109 +10,23 @@ void main() {
class MyApp extends StatelessWidget {
const MyApp({super.key});
- // This widget is the root of your application.
@override
Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- // This is the theme of your application.
- //
- // Try running your application with "flutter run". You'll see the
- // application has a blue toolbar. Then, without quitting the app, try
- // changing the primarySwatch below to Colors.green and then invoke
- // "hot reload" (press "r" in the console where you ran "flutter run",
- // or simply save your changes to "hot reload" in a Flutter IDE).
- // Notice that the counter didn't reset back to zero; the application
- // is not restarted.
- primarySwatch: Colors.blue,
- ),
- home: const MyHomePage(title: 'Flutter Demo Home Page'),
- );
- }
-}
-
-class MyHomePage extends StatefulWidget {
- const MyHomePage({super.key, required this.title});
-
- // This widget is the home page of your application. It is stateful, meaning
- // that it has a State object (defined below) that contains fields that affect
- // how it looks.
-
- // This class is the configuration for the state. It holds the values (in this
- // case the title) provided by the parent (in this case the App widget) and
- // used by the build method of the State. Fields in a Widget subclass are
- // always marked "final".
-
- final String title;
-
- @override
- State createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State {
- int _counter = 0;
-
- void _incrementCounter() {
- setState(() {
- // This call to setState tells the Flutter framework that something has
- // changed in this State, which causes it to rerun the build method below
- // so that the display can reflect the updated values. If we changed
- // _counter without calling setState(), then the build method would not be
- // called again, and so nothing would appear to happen.
- _counter++;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- // This method is rerun every time setState is called, for instance as done
- // by the _incrementCounter method above.
- //
- // The Flutter framework has been optimized to make rerunning build methods
- // fast, so that you can just rebuild anything that needs updating rather
- // than having to individually change instances of widgets.
- return Scaffold(
- appBar: AppBar(
- // Here we take the value from the MyHomePage object that was created by
- // the App.build method, and use it to set our appbar title.
- title: Text(widget.title),
- ),
- body: Center(
- // Center is a layout widget. It takes a single child and positions it
- // in the middle of the parent.
- child: Column(
- // Column is also a layout widget. It takes a list of children and
- // arranges them vertically. By default, it sizes itself to fit its
- // children horizontally, and tries to be as tall as its parent.
- //
- // Invoke "debug painting" (press "p" in the console, choose the
- // "Toggle Debug Paint" action from the Flutter Inspector in Android
- // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
- // to see the wireframe for each widget.
- //
- // Column has various properties to control how it sizes itself and
- // how it positions its children. Here we use mainAxisAlignment to
- // center the children vertically; the main axis here is the vertical
- // axis because Columns are vertical (the cross axis would be
- // horizontal).
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '$_counter',
- style: Theme.of(context).textTheme.headlineMedium,
- ),
- ],
+ return Provider(
+ create: (_) {
+ CookieRequest request = CookieRequest();
+ return request;
+ },
+ child: MaterialApp(
+ title: 'Golek Makan Rek',
+ theme: ThemeData(
+ useMaterial3: true,
+ colorScheme: ColorScheme.fromSeed(
+ seedColor: const Color.fromARGB(255, 255, 179, 0),
+ ).copyWith(secondary: const Color(0xFFFFFFFF)),
),
+ home: const LoginPage(),
),
- floatingActionButton: FloatingActionButton(
- onPressed: _incrementCounter,
- tooltip: 'Increment',
- child: const Icon(Icons.add),
- ), // This trailing comma makes auto-formatting nicer for build methods.
);
}
-}
+}
\ No newline at end of file
diff --git a/lib/models/allmodels.dart b/lib/models/allmodels.dart
new file mode 100644
index 0000000..74c94f7
--- /dev/null
+++ b/lib/models/allmodels.dart
@@ -0,0 +1,259 @@
+// // To parse this JSON data, do
+// //
+// // final post = postFromJson(jsonString);
+
+// import 'dart:convert';
+
+// List postFromJson(String str) => List.from(json.decode(str).map((x) => Post.fromJson(x)));
+
+// String postToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+// class Post {
+// String model;
+// int pk;
+// Fields fields;
+
+// Post({
+// required this.model,
+// required this.pk,
+// required this.fields,
+// });
+
+// factory Post.fromJson(Map json) => Post(
+// model: json["model"],
+// pk: json["pk"],
+// fields: Fields.fromJson(json["fields"]),
+// );
+
+// Map toJson() => {
+// "model": model,
+// "pk": pk,
+// "fields": fields.toJson(),
+// };
+// }
+
+// class Fields {
+// int user;
+// String text;
+// String image;
+// int likeCount;
+// int commentCount;
+// int shareCount;
+// int reportCount;
+// DateTime createdAt;
+// String restaurant;
+
+// Fields({
+// required this.user,
+// required this.text,
+// required this.image,
+// required this.likeCount,
+// required this.commentCount,
+// required this.shareCount,
+// required this.reportCount,
+// required this.createdAt,
+// required this.restaurant,
+// });
+
+// factory Fields.fromJson(Map json) => Fields(
+// user: json["user"],
+// text: json["text"],
+// image: json["image"],
+// likeCount: json["like_count"],
+// commentCount: json["comment_count"],
+// shareCount: json["share_count"],
+// reportCount: json["report_count"],
+// createdAt: DateTime.parse(json["created_at"]),
+// restaurant: json["restaurant"],
+// );
+
+// Map toJson() => {
+// "user": user,
+// "text": text,
+// "image": image,
+// "like_count": likeCount,
+// "comment_count": commentCount,
+// "share_count": shareCount,
+// "report_count": reportCount,
+// "created_at": createdAt.toIso8601String(),
+// "restaurant": restaurant,
+// };
+// }
+
+// // To parse this JSON data, do
+// //
+// // final like = likeFromJson(jsonString);
+
+
+// List likeFromJson(String str) => List.from(json.decode(str).map((x) => Like.fromJson(x)));
+
+// String likeToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+// class Like {
+// String model;
+// int pk;
+// Fields fields;
+
+// Like({
+// required this.model,
+// required this.pk,
+// required this.fields,
+// });
+
+// factory Like.fromJson(Map json) => Like(
+// model: json["model"],
+// pk: json["pk"],
+// fields: Fields.fromJson(json["fields"]),
+// );
+
+// Map toJson() => {
+// "model": model,
+// "pk": pk,
+// "fields": fields.toJson(),
+// };
+// }
+
+// class Fields {
+// int post;
+// int user;
+// DateTime createdAt;
+
+// Fields({
+// required this.post,
+// required this.user,
+// required this.createdAt,
+// });
+
+// factory Fields.fromJson(Map json) => Fields(
+// post: json["post"],
+// user: json["user"],
+// createdAt: DateTime.parse(json["created_at"]),
+// );
+
+// Map toJson() => {
+// "post": post,
+// "user": user,
+// "created_at": createdAt.toIso8601String(),
+// };
+// }
+
+// // To parse this JSON data, do
+// //
+// // final comment = commentFromJson(jsonString);
+
+// import 'dart:convert';
+
+// List commentFromJson(String str) => List.from(json.decode(str).map((x) => Comment.fromJson(x)));
+
+// String commentToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+// class Comment {
+// String model;
+// int pk;
+// Fields fields;
+
+// Comment({
+// required this.model,
+// required this.pk,
+// required this.fields,
+// });
+
+// factory Comment.fromJson(Map json) => Comment(
+// model: json["model"],
+// pk: json["pk"],
+// fields: Fields.fromJson(json["fields"]),
+// );
+
+// Map toJson() => {
+// "model": model,
+// "pk": pk,
+// "fields": fields.toJson(),
+// };
+// }
+
+// class Fields {
+// int post;
+// int user;
+// String text;
+// DateTime createdAt;
+
+// Fields({
+// required this.post,
+// required this.user,
+// required this.text,
+// required this.createdAt,
+// });
+
+// factory Fields.fromJson(Map json) => Fields(
+// post: json["post"],
+// user: json["user"],
+// text: json["text"],
+// createdAt: DateTime.parse(json["created_at"]),
+// );
+
+// Map toJson() => {
+// "post": post,
+// "user": user,
+// "text": text,
+// "created_at": createdAt.toIso8601String(),
+// };
+// }
+
+// // To parse this JSON data, do
+// //
+// // final like = likeFromJson(jsonString);
+
+// import 'dart:convert';
+
+// List likeFromJson(String str) => List.from(json.decode(str).map((x) => Like.fromJson(x)));
+
+// String likeToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+// class Like {
+// String model;
+// int pk;
+// Fields fields;
+
+// Like({
+// required this.model,
+// required this.pk,
+// required this.fields,
+// });
+
+// factory Like.fromJson(Map json) => Like(
+// model: json["model"],
+// pk: json["pk"],
+// fields: Fields.fromJson(json["fields"]),
+// );
+
+// Map toJson() => {
+// "model": model,
+// "pk": pk,
+// "fields": fields.toJson(),
+// };
+// }
+
+// class Fields {
+// int post;
+// int user;
+// DateTime createdAt;
+
+// Fields({
+// required this.post,
+// required this.user,
+// required this.createdAt,
+// });
+
+// factory Fields.fromJson(Map json) => Fields(
+// post: json["post"],
+// user: json["user"],
+// createdAt: DateTime.parse(json["created_at"]),
+// );
+
+// Map toJson() => {
+// "post": post,
+// "user": user,
+// "created_at": createdAt.toIso8601String(),
+// };
+// }
+
diff --git a/lib/models/comment.dart b/lib/models/comment.dart
new file mode 100644
index 0000000..35d6170
--- /dev/null
+++ b/lib/models/comment.dart
@@ -0,0 +1,61 @@
+// To parse this JSON data, do
+//
+// final comment = commentFromJson(jsonString);
+
+import 'dart:convert';
+
+List commentFromJson(String str) => List.from(json.decode(str).map((x) => Comment.fromJson(x)));
+
+String commentToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+class Comment {
+ String model;
+ int pk;
+ CommentFields fields;
+
+ Comment({
+ required this.model,
+ required this.pk,
+ required this.fields,
+ });
+
+ factory Comment.fromJson(Map json) => Comment(
+ model: json["model"],
+ pk: json["pk"],
+ fields: CommentFields.fromJson(json["fields"]),
+ );
+
+ Map toJson() => {
+ "model": model,
+ "pk": pk,
+ "fields": fields.toJson(),
+ };
+}
+
+class CommentFields {
+ int post;
+ int user;
+ String text;
+ DateTime createdAt;
+
+ CommentFields({
+ required this.post,
+ required this.user,
+ required this.text,
+ required this.createdAt,
+ });
+
+ factory CommentFields.fromJson(Map json) => CommentFields(
+ post: json["post"],
+ user: json["user"],
+ text: json["text"],
+ createdAt: DateTime.parse(json["created_at"]),
+ );
+
+ Map toJson() => {
+ "post": post,
+ "user": user,
+ "text": text,
+ "created_at": createdAt.toIso8601String(),
+ };
+}
diff --git a/lib/models/food.dart b/lib/models/food.dart
new file mode 100644
index 0000000..12fbcaa
--- /dev/null
+++ b/lib/models/food.dart
@@ -0,0 +1,63 @@
+// To parse this JSON data, do
+//
+// final welcome = welcomeFromJson(jsonString);
+
+class Welcome {
+ String model;
+ String pk;
+ Fields fields;
+
+ Welcome({
+ required this.model,
+ required this.pk,
+ required this.fields,
+ });
+
+ factory Welcome.fromJson(Map json) => Welcome(
+ model: json["model"],
+ pk: json["pk"],
+ fields: Fields.fromJson(json["fields"]),
+ );
+
+ Map toJson() => {
+ "model": model,
+ "pk": pk,
+ "fields": fields.toJson(),
+ };
+}
+
+class Fields {
+ String nama;
+ String kategori;
+ int harga;
+ int diskon;
+ String deskripsi;
+ String restoran;
+
+ Fields({
+ required this.nama,
+ required this.kategori,
+ required this.harga,
+ required this.diskon,
+ required this.deskripsi,
+ required this.restoran,
+ });
+
+ factory Fields.fromJson(Map json) => Fields(
+ nama: json["nama"],
+ kategori: json["kategori"],
+ harga: json["harga"],
+ diskon: json["diskon"],
+ deskripsi: json["deskripsi"],
+ restoran: json["restoran"],
+ );
+
+ Map toJson() => {
+ "nama": nama,
+ "kategori": kategori,
+ "harga": harga,
+ "diskon": diskon,
+ "deskripsi": deskripsi,
+ "restoran": restoran,
+ };
+}
\ No newline at end of file
diff --git a/lib/models/like.dart b/lib/models/like.dart
new file mode 100644
index 0000000..4598801
--- /dev/null
+++ b/lib/models/like.dart
@@ -0,0 +1,57 @@
+// To parse this JSON data, do
+//
+// final like = likeFromJson(jsonString);
+
+import 'dart:convert';
+
+List likeFromJson(String str) => List.from(json.decode(str).map((x) => Like.fromJson(x)));
+
+String likeToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+class Like {
+ String model;
+ int pk;
+ LikeFields fields;
+
+ Like({
+ required this.model,
+ required this.pk,
+ required this.fields,
+ });
+
+ factory Like.fromJson(Map json) => Like(
+ model: json["model"],
+ pk: json["pk"],
+ fields: LikeFields.fromJson(json["fields"]),
+ );
+
+ Map toJson() => {
+ "model": model,
+ "pk": pk,
+ "fields": fields.toJson(),
+ };
+}
+
+class LikeFields {
+ int post;
+ int user;
+ DateTime createdAt;
+
+ LikeFields({
+ required this.post,
+ required this.user,
+ required this.createdAt,
+ });
+
+ factory LikeFields.fromJson(Map json) => LikeFields(
+ post: json["post"],
+ user: json["user"],
+ createdAt: DateTime.parse(json["created_at"]),
+ );
+
+ Map toJson() => {
+ "post": post,
+ "user": user,
+ "created_at": createdAt.toIso8601String(),
+ };
+}
diff --git a/lib/models/post.dart b/lib/models/post.dart
new file mode 100644
index 0000000..c3a9532
--- /dev/null
+++ b/lib/models/post.dart
@@ -0,0 +1,90 @@
+// To parse this JSON data, do
+//
+// final post = postFromJson(jsonString);
+
+import 'dart:convert';
+
+import 'package:golekmakanrek_mobile/models/comment.dart';
+
+List postFromJson(String str) => List.from(json.decode(str).map((x) => Post.fromJson(x)));
+
+String postToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+class Post {
+ String model;
+ int pk;
+ PostFields fields;
+
+ Post({
+ required this.model,
+ required this.pk,
+ required this.fields,
+ });
+
+ factory Post.fromJson(Map json) => Post(
+ model: json["model"],
+ pk: json["pk"],
+ fields: PostFields.fromJson(json["fields"]),
+ );
+
+ Map toJson() => {
+ "model": model,
+ "pk": pk,
+ "fields": fields.toJson(),
+ };
+
+ copyWith({required fields}) {}
+}
+
+class PostFields {
+ int user;
+ String text;
+ String? image;
+ int likeCount;
+ int commentCount;
+ int shareCount;
+ int reportCount;
+ DateTime createdAt;
+ String? restaurant;
+ bool? isLiked;
+
+ PostFields({
+ required this.user,
+ required this.text,
+ required this.image,
+ required this.likeCount,
+ required this.commentCount,
+ required this.shareCount,
+ required this.reportCount,
+ required this.createdAt,
+ required this.restaurant,
+ });
+
+ factory PostFields.fromJson(Map json) => PostFields(
+ user: json["user"],
+ text: json["text"],
+ image: json["image"],
+ likeCount: json["like_count"],
+ commentCount: json["comment_count"],
+ shareCount: json["share_count"],
+ reportCount: json["report_count"],
+ createdAt: DateTime.parse(json["created_at"]),
+ restaurant: json["restaurant"],
+ );
+
+ get comments => null;
+
+ Map toJson() => {
+ "user": user,
+ "text": text,
+ "image": image,
+ "like_count": likeCount,
+ "comment_count": commentCount,
+ "share_count": shareCount,
+ "report_count": reportCount,
+ "created_at": createdAt.toIso8601String(),
+ "restaurant": restaurant,
+ };
+
+ copyWith({required List comments}) {}
+}
diff --git a/lib/models/report.dart b/lib/models/report.dart
new file mode 100644
index 0000000..664f0f8
--- /dev/null
+++ b/lib/models/report.dart
@@ -0,0 +1,61 @@
+// To parse this JSON data, do
+//
+// final report = reportFromJson(jsonString);
+
+import 'dart:convert';
+
+List reportFromJson(String str) => List.from(json.decode(str).map((x) => Report.fromJson(x)));
+
+String reportToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+class Report {
+ String model;
+ int pk;
+ ReportFields fields;
+
+ Report({
+ required this.model,
+ required this.pk,
+ required this.fields,
+ });
+
+ factory Report.fromJson(Map json) => Report(
+ model: json["model"],
+ pk: json["pk"],
+ fields: ReportFields.fromJson(json["fields"]),
+ );
+
+ Map toJson() => {
+ "model": model,
+ "pk": pk,
+ "fields": fields.toJson(),
+ };
+}
+
+class ReportFields {
+ int post;
+ int reportedBy;
+ String reason;
+ DateTime createdAt;
+
+ ReportFields({
+ required this.post,
+ required this.reportedBy,
+ required this.reason,
+ required this.createdAt,
+ });
+
+ factory ReportFields.fromJson(Map json) => ReportFields(
+ post: json["post"],
+ reportedBy: json["reported_by"],
+ reason: json["reason"],
+ createdAt: DateTime.parse(json["created_at"]),
+ );
+
+ Map toJson() => {
+ "post": post,
+ "reported_by": reportedBy,
+ "reason": reason,
+ "created_at": createdAt.toIso8601String(),
+ };
+}
diff --git a/lib/models/restaurant.dart b/lib/models/restaurant.dart
new file mode 100644
index 0000000..3a8274c
--- /dev/null
+++ b/lib/models/restaurant.dart
@@ -0,0 +1,53 @@
+import 'dart:convert';
+
+List restaurantFromJson(String str) => List.from(json.decode(str).map((x) => Restaurant.fromJson(x)));
+
+String restaurantToJson(List data) => json.encode(List.from(data.map((x) => x.toJson())));
+
+class Restaurant {
+ String model;
+ String pk;
+ Fields fields;
+
+ Restaurant({
+ required this.model,
+ required this.pk,
+ required this.fields,
+ });
+
+ factory Restaurant.fromJson(Map json) => Restaurant(
+ model: json["model"],
+ pk: json["pk"],
+ fields: Fields.fromJson(json["fields"]),
+ );
+
+ Map toJson() => {
+ "model": model,
+ "pk": pk,
+ "fields": fields.toJson(),
+ };
+}
+
+class Fields {
+ String nama;
+ String kategori;
+ String deskripsi;
+
+ Fields({
+ required this.nama,
+ required this.kategori,
+ required this.deskripsi,
+ });
+
+ factory Fields.fromJson(Map json) => Fields(
+ nama: json["nama"],
+ kategori: json["kategori"],
+ deskripsi: json["deskripsi"],
+ );
+
+ Map toJson() => {
+ "nama": nama,
+ "kategori": kategori,
+ "deskripsi": deskripsi,
+ };
+}
\ No newline at end of file
diff --git a/lib/screens/forum/forum.dart b/lib/screens/forum/forum.dart
new file mode 100644
index 0000000..444da34
--- /dev/null
+++ b/lib/screens/forum/forum.dart
@@ -0,0 +1,881 @@
+import 'package:flutter/material.dart';
+import 'package:golekmakanrek_mobile/models/post.dart';
+import 'package:golekmakanrek_mobile/models/comment.dart';
+import 'package:golekmakanrek_mobile/screens/forum/post_form.dart';
+import 'package:pbp_django_auth/pbp_django_auth.dart';
+import 'package:provider/provider.dart';
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+
+// Function to fetch the current username
+Future fetchUsername(CookieRequest request) async {
+ try {
+ // Fetch username from the API
+ final response = await request.get(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/auth/get_username/',
+ );
+
+ // Extract and return the username
+ if (response['username'] != null) {
+ return response['username'];
+ } else {
+ throw Exception("Invalid response: No username found");
+ }
+ } catch (e) {
+ throw Exception("Failed to fetch username: $e");
+ }
+}
+
+class ForumPage extends StatefulWidget {
+ const ForumPage({super.key});
+
+ @override
+ State createState() => _ForumPageState();
+}
+
+class _ForumPageState extends State {
+ List _posts = [];
+ Map _users = {};
+ Map> postComments = {};
+ Map _restaurants = {};
+ bool _isLoading = true;
+ String _errorMessage = '';
+ bool _isFetching = false;
+
+ // To store the username
+ String currentUsername = '';
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final request = context.read();
+ initializePage(request); // Fetch username and data
+ });
+ }
+
+ // --------------------------------------------------------
+ // INITIALIZE PAGE (FETCH USERNAME AND DATA)
+ // --------------------------------------------------------
+ Future initializePage(CookieRequest request) async {
+ try {
+ setState(() => _isLoading = true);
+
+ // Fetch the current username
+ currentUsername = await fetchUsername(request);
+
+ // Fetch other data
+ await fetchData(request);
+ } catch (e) {
+ setState(() {
+ _errorMessage = 'Error initializing page: $e';
+ _isLoading = false;
+ });
+ }
+ }
+
+ // --------------------------------------------------------
+ // FETCH DATA (POSTS, COMMENTS, USERS, RESTAURANTS)
+ // --------------------------------------------------------
+ Future fetchData(CookieRequest request) async {
+ if (_isFetching) return;
+ _isFetching = true;
+ try {
+ // Fetch posts
+ final postResponse = await request.get(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/post_json/',
+ );
+ List postData = postResponse;
+
+ // Fetch comments
+ final commentResponse = await request.get(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/comment_json/',
+ );
+ List commentData = commentResponse;
+
+ // Fetch user list
+ final userResponse = await request.get(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/get_all_users/',
+ );
+ List userData = userResponse['users'];
+
+ // Fetch restaurants
+ final restaurantResponse = await request.get(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/restaurant_json/',
+ );
+ List restaurantData = restaurantResponse;
+
+ // Map userId to username
+ Map fetchedUsers = {};
+ for (var user in userData) {
+ int userId = user['id'];
+ String username = user['username'];
+ fetchedUsers[userId] = username;
+ }
+
+ // Convert post data to Post objects
+ List fetchedPosts =
+ postData.map((item) => Post.fromJson(item)).toList();
+
+ // Map postId to list of Comments
+ Map> commentsMap = {};
+ for (var item in commentData) {
+ Comment comment = Comment.fromJson(item);
+ if (comment.fields.post != null) {
+ commentsMap.putIfAbsent(comment.fields.post!, () => []).add(comment);
+ }
+ }
+
+ // Map restaurant ID to restaurant name
+ Map fetchedRestaurants = {};
+ for (var item in restaurantData) {
+ String restaurantId = item['pk'].toString();
+ String restaurantName = item['fields']['nama'];
+ fetchedRestaurants[restaurantId] = restaurantName;
+ }
+
+ setState(() {
+ _users = fetchedUsers;
+ _posts = fetchedPosts;
+ postComments = commentsMap;
+ _restaurants = fetchedRestaurants;
+ _isLoading = false;
+ });
+ } catch (e) {
+ setState(() {
+ _errorMessage = 'Error fetching data: $e';
+ _isLoading = false;
+ });
+ } finally {
+ _isFetching = false;
+ }
+ }
+
+ // --------------------------------------------------------
+ // REFRESH DATA
+ // --------------------------------------------------------
+ Future _refreshData() async {
+ setState(() {
+ _isLoading = true;
+ _errorMessage = '';
+ });
+ final request = context.read();
+ await initializePage(request);
+ }
+
+ // --------------------------------------------------------
+ // BUILD WIDGET TREE
+ // --------------------------------------------------------
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(
+ currentUsername.isNotEmpty
+ ? "Welcome, $currentUsername!"
+ : "Jelajahi Makanan di Surabaya!",
+ ),
+ backgroundColor: Colors.orange,
+ centerTitle: true,
+ ),
+ body: _isLoading
+ ? const Center(child: CircularProgressIndicator())
+ : _errorMessage.isNotEmpty
+ ? _buildErrorView()
+ : Column(
+ children: [
+ // ---------------------------------------
+ // Header Section
+ // ---------------------------------------
+ Container(
+ width: double.infinity,
+ color: Colors.orange.shade100,
+ padding: const EdgeInsets.symmetric(
+ vertical: 16, horizontal: 16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Text(
+ "Jelajahi Makanan di Surabaya!",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: Colors.orange,
+ ),
+ ),
+ const SizedBox(height: 4),
+ const Text(
+ "Berbagi cerita bersama kulineran di Surabaya!",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Colors.black87,
+ ),
+ ),
+ const SizedBox(height: 12),
+ ElevatedButton(
+ onPressed: () {
+ // Navigate to Post Form Page
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const CreatePostPage()),
+ ).then((value) => _refreshData());
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.orange,
+ padding: const EdgeInsets.symmetric(
+ horizontal: 20, vertical: 10),
+ ),
+ child: const Text(
+ "Posting di Forum",
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ // ---------------------------------------
+ // Posts List View
+ // ---------------------------------------
+ Expanded(child: _buildPostListView()),
+ ],
+ ),
+ );
+ }
+
+ // --------------------------------------------------------
+ // ERROR VIEW
+ // --------------------------------------------------------
+ Widget _buildErrorView() {
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text(
+ "Terjadi Kesalahan:",
+ style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ _errorMessage,
+ style: const TextStyle(fontSize: 14, color: Colors.red),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: _refreshData,
+ style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
+ child: const Text("Coba Lagi"),
+ ),
+ ],
+ ),
+ );
+ }
+
+ // --------------------------------------------------------
+ // POSTS LIST VIEW
+ // --------------------------------------------------------
+ Widget _buildPostListView() {
+ return RefreshIndicator(
+ onRefresh: _refreshData,
+ child: ListView.builder(
+ itemCount: _posts.length,
+ itemBuilder: (context, index) {
+ final post = _posts[index];
+ final comments = postComments[post.pk] ?? [];
+ final username = _users[post.fields.user] ?? 'Unknown User';
+
+ // Restaurant name (if any)
+ final restaurantName = post.fields.restaurant != null
+ ? _restaurants[post.fields.restaurant] ?? 'Unknown Restaurant'
+ : null;
+
+ return PostCard(
+ post: post,
+ comments: comments,
+ username: username,
+ onPostUpdated: _refreshData,
+ currentUsername: currentUsername,
+ restaurantName: restaurantName,
+ usersMap: _users, // Pass the users map to PostCard
+ );
+ },
+ ),
+ );
+ }
+}
+
+// ---------------------------------------------------------------------------
+// UTILITY: sharePost (you can still adapt this to actually copy link, etc.)
+// ---------------------------------------------------------------------------
+void sharePost(BuildContext context) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text("URL telah disalin!")),
+ );
+}
+
+// ---------------------------------------------------------------------------
+// PostCard: menampilkan detail Post + Aksi
+// ---------------------------------------------------------------------------
+class PostCard extends StatefulWidget {
+ final Post post;
+ final List comments;
+ final String username; // Display name of the post owner
+ final VoidCallback onPostUpdated;
+ final String currentUsername; // Username of the currently logged-in user
+ final String? restaurantName;
+ final Map usersMap; // Map of userId to username
+
+ const PostCard({
+ Key? key,
+ required this.post,
+ required this.comments,
+ required this.username,
+ required this.onPostUpdated,
+ required this.currentUsername,
+ required this.restaurantName,
+ required this.usersMap,
+ }) : super(key: key);
+
+ @override
+ State createState() => _PostCardState();
+}
+
+class _PostCardState extends State {
+ bool showComments = false;
+ bool isLiked = false;
+ int likeCount = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ likeCount = widget.post.fields.likeCount;
+ }
+
+ // --------------------------------------------------------
+ // TOGGLE LIKE
+ // --------------------------------------------------------
+ Future toggleLike() async {
+ try {
+ final request = context.read();
+ final response = await request.post(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/like_post_flutter/',
+ {
+ 'post_id': widget.post.pk.toString(),
+ },
+ );
+ if (response['status'] == 'success') {
+ setState(() {
+ isLiked = response['liked'];
+ likeCount = response['like_count'];
+ });
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Gagal like: ${response['message']}")),
+ );
+ }
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Error like: $e")),
+ );
+ }
+ }
+
+ // --------------------------------------------------------
+ // DELETE POST
+ // --------------------------------------------------------
+ Future deletePost() async {
+ try {
+ final request = context.read();
+ final response = await request.post(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/delete_post_flutter/',
+ {
+ 'post_id': widget.post.pk.toString(),
+ },
+ );
+ if (response['status'] == 'success') {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text("Post berhasil dihapus!")),
+ );
+ widget.onPostUpdated(); // Refresh the posts list
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Gagal menghapus post: ${response['message']}")),
+ );
+ }
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Error menghapus post: $e")),
+ );
+ }
+ }
+
+ // --------------------------------------------------------
+ // POST COMMENT
+ // --------------------------------------------------------
+ Future postComment(String commentText) async {
+ try {
+ final request = context.read();
+ final response = await request.post(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/comment_post_flutter/',
+ {
+ 'post_id': widget.post.pk.toString(),
+ 'comment': commentText,
+ },
+ );
+
+ if (response['status'] == 'success') {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text("Komentar berhasil ditambahkan!")),
+ );
+ // Add the new comment to the local list
+ final newComment = Comment.fromJson(response['comment']);
+ setState(() {
+ widget.comments.add(newComment);
+ });
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Gagal komentar: ${response['message']}")),
+ );
+ }
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Error komentar: $e")),
+ );
+ }
+ }
+
+ // --------------------------------------------------------
+ // BUILD
+ // --------------------------------------------------------
+ @override
+ Widget build(BuildContext context) {
+ final post = widget.post;
+ final comments = widget.comments;
+ bool canDeletePost = (widget.username == widget.currentUsername);
+
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
+ child: Card(
+ // White card on top of grey background
+ color: Colors.white,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+ elevation: 2,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // ---------------------------------------
+ // Top Row: Profile icon + name/date (left), Delete button (right if canDelete)
+ // ---------------------------------------
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Profile icon
+ const CircleAvatar(
+ radius: 18,
+ backgroundColor: Colors.orange,
+ child: Icon(Icons.person, color: Colors.white),
+ ),
+
+ const SizedBox(width: 8),
+
+ // Username & date (left)
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ widget.username,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ color: Colors.black,
+ ),
+ ),
+ const SizedBox(height: 2),
+ Text(
+ _formatDate(post.fields.createdAt),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontSize: 12,
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ // Delete button on the right if user owns the post
+ if (canDeletePost)
+ IconButton(
+ onPressed: deletePost,
+ icon: const Icon(
+ Icons.delete_forever,
+ color: Colors.red,
+ ),
+ ),
+ ],
+ ),
+
+ // ---------------------------------------
+ // Restaurant name (if present)
+ // ---------------------------------------
+ if (widget.restaurantName != null)
+ Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: Text(
+ widget.restaurantName!,
+ style: const TextStyle(
+ fontStyle: FontStyle.italic,
+ color: Colors.orange,
+ fontSize: 14,
+ ),
+ ),
+ ),
+
+ // ---------------------------------------
+ // Post text
+ // ---------------------------------------
+ if (post.fields.text.isNotEmpty) ...[
+ const SizedBox(height: 8),
+ Text(
+ post.fields.text,
+ style: const TextStyle(fontSize: 14, color: Colors.black),
+ ),
+ ],
+
+ // ---------------------------------------
+ // Post image (if any)
+ // ---------------------------------------
+ if (post.fields.image != null && post.fields.image!.isNotEmpty)
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: Image.network(post.fields.image!),
+ ),
+
+ const SizedBox(height: 8),
+
+ // ---------------------------------------
+ // Action row: Like, Comment, Share, Report
+ // ---------------------------------------
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween, // Evenly space icons
+ children: [
+ IconButton(
+ onPressed: toggleLike,
+ icon: Icon(
+ isLiked ? Icons.thumb_up : Icons.thumb_up_alt_outlined,
+ color: isLiked ? Colors.blue : Colors.grey,
+ ),
+ ),
+ IconButton(
+ onPressed: () => setState(() {
+ showComments = !showComments;
+ }),
+ icon: const Icon(Icons.comment_outlined),
+ ),
+ IconButton(
+ onPressed: () => sharePost(context),
+ icon: const Icon(Icons.share_outlined),
+ ),
+ IconButton(
+ onPressed: _showReportDialog, // Call the dialog function
+ icon: const Icon(Icons.flag_outlined, color: Colors.red),
+ ),
+ ],
+ ),
+
+ // ---------------------------------------
+ // Comments Section with Divider
+ // ---------------------------------------
+ if (showComments) ...[
+ const Divider(),
+ _buildCommentsSection(comments),
+ ],
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ // --------------------------------------------------------
+ // COMMENTS SECTION
+ // --------------------------------------------------------
+ Widget _buildCommentsSection(List comments) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 8),
+ ...comments.map((c) => _buildCommentItem(c)).toList(),
+ const SizedBox(height: 8),
+ _buildCommentInput(),
+ ],
+ );
+ }
+
+ // --------------------------------------------------------
+ // BUILD SINGLE COMMENT ITEM
+ // --------------------------------------------------------
+ Widget _buildCommentItem(Comment comment) {
+ String commenterUsername =
+ widget.usersMap[comment.fields.user] ?? 'Unknown User';
+ bool canEditComment = (commenterUsername == widget.currentUsername);
+
+ return Container(
+ margin: const EdgeInsets.only(bottom: 8),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Profile icon for commenter
+ const CircleAvatar(
+ radius: 12,
+ backgroundColor: Colors.orange,
+ child: Icon(Icons.person, color: Colors.white, size: 14),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Username in bold
+ Text(
+ "$commenterUsername: ",
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 14,
+ color: Colors.black,
+ ),
+ ),
+ // Comment text
+ Expanded(
+ child: Text(
+ comment.fields.text,
+ style: const TextStyle(color: Colors.black, fontSize: 14),
+ ),
+ ),
+ ],
+ ),
+ ),
+ // Edit icon (blue) if user can edit
+ if (canEditComment)
+ IconButton(
+ icon: const Icon(
+ Icons.edit,
+ color: Colors.blue,
+ size: 20,
+ ),
+ onPressed: () => _editCommentDialog(comment),
+ padding: EdgeInsets.zero,
+ constraints: const BoxConstraints(),
+ ),
+ ],
+ ),
+ );
+}
+
+ // --------------------------------------------------------
+ // COMMENT INPUT
+ // --------------------------------------------------------
+ Widget _buildCommentInput() {
+ final TextEditingController commentController = TextEditingController();
+ return Row(
+ children: [
+ Expanded(
+ child: TextField(
+ controller: commentController,
+ decoration: const InputDecoration(
+ hintText: "Tambah komentar...",
+ ),
+ ),
+ ),
+ IconButton(
+ onPressed: () {
+ final text = commentController.text.trim();
+ if (text.isNotEmpty) {
+ postComment(text);
+ commentController.clear();
+ }
+ },
+ icon: const Icon(Icons.send),
+ ),
+ ],
+ );
+ }
+
+ // --------------------------------------------------------
+ // EDIT COMMENT DIALOG
+ // --------------------------------------------------------
+ void _editCommentDialog(Comment comment) {
+ final TextEditingController editController =
+ TextEditingController(text: comment.fields.text);
+
+ showDialog(
+ context: context,
+ builder: (BuildContext ctx) {
+ return AlertDialog(
+ title: const Text("Edit Komentar"),
+ content: TextField(
+ controller: editController,
+ decoration: const InputDecoration(
+ hintText: "Masukkan komentar baru...",
+ ),
+ maxLines: 3,
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text("Batal"),
+ ),
+ TextButton(
+ onPressed: () async {
+ Navigator.pop(context);
+ await _editComment(comment, editController.text.trim());
+ },
+ child: const Text("Simpan"),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ // --------------------------------------------------------
+ // EDIT COMMENT LOGIC
+ // --------------------------------------------------------
+ Future _editComment(Comment comment, String newContent) async {
+ if (newContent.isEmpty) return;
+ try {
+ final request = context.read();
+ final response = await request.post(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/edit_comment_flutter/',
+ {
+ 'comment_id': comment.pk.toString(),
+ 'text': newContent,
+ },
+ );
+
+ if (response['status'] == 'success') {
+ setState(() {
+ // Update comment text locally
+ comment.fields.text = newContent;
+ });
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text("Komentar berhasil diedit!")),
+ );
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Gagal edit komentar: ${response['message']}")),
+ );
+ }
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Error edit komentar: $e")),
+ );
+ }
+ }
+
+ // --------------------------------------------------------
+ // FORMAT DATE
+ // --------------------------------------------------------
+ String _formatDate(DateTime date) {
+ return "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
+ }
+
+ // --------------------------------------------------------
+ // REPORT POST
+ // --------------------------------------------------------
+ Future reportPost(String reason) async {
+ try {
+ final request = context.read();
+ final response = await request.post(
+ 'https://ideal-eureka-r4gxwv6xrv5gh5565-8000.app.github.dev/forum/report_post_flutter/',
+ {
+ 'post_id': widget.post.pk.toString(),
+ 'reason': reason,
+ },
+ );
+
+ if (response['status'] == 'success') {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ response['message'] ?? "Postingan berhasil dilaporkan!")),
+ );
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ "Gagal melaporkan: ${response['message']}")),
+ );
+ }
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text("Error melaporkan post: $e")),
+ );
+ }
+ }
+
+ // --------------------------------------------------------
+ // SHOW REPORT DIALOG
+ // --------------------------------------------------------
+ void _showReportDialog() {
+ String selectedReason = "Konten Tidak Pantas"; // Default selected reason
+ showDialog(
+ context: context,
+ builder: (BuildContext ctx) {
+ return AlertDialog(
+ title: const Text("Laporkan Postingan"),
+ content: StatefulBuilder(
+ builder: (BuildContext context, StateSetter setStateSB) {
+ return DropdownButton(
+ value: selectedReason,
+ isExpanded: true,
+ items: [
+ "Konten Tidak Pantas",
+ "Spam atau Iklan",
+ "Bahasa Kasar atau Menyinggung",
+ "Misinformasi",
+ "Topik Tidak Relevan"
+ ].map>((String value) {
+ return DropdownMenuItem(
+ value: value,
+ child: Text(value),
+ );
+ }).toList(),
+ onChanged: (String? newVal) {
+ setStateSB(() {
+ selectedReason = newVal ?? "Konten Tidak Pantas";
+ });
+ },
+ );
+ },
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context), // Close the dialog
+ child: const Text("Batal"),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context); // Close the dialog
+ reportPost(selectedReason); // Call the report function with the selected reason
+ },
+ child: const Text("Kirim"),
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Example: CreatePostPage (PostForm)
+// ---------------------------------------------------------------------------
diff --git a/lib/screens/forum/post_form.dart b/lib/screens/forum/post_form.dart
new file mode 100644
index 0000000..ab889fe
--- /dev/null
+++ b/lib/screens/forum/post_form.dart
@@ -0,0 +1,212 @@
+import 'dart:async';
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'package:pbp_django_auth/pbp_django_auth.dart';
+import 'package:provider/provider.dart';
+
+class CreatePostPage extends StatefulWidget {
+ const CreatePostPage({super.key});
+
+ @override
+ State createState() => _CreatePostPageState();
+}
+
+class _CreatePostPageState extends State {
+ final _formKey = GlobalKey();
+ final TextEditingController _textController = TextEditingController();
+ final TextEditingController _searchController = TextEditingController();
+
+ bool _isLoading = false;
+ List