Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build and Release IPA
name: Build and Release

on:
push:
Expand All @@ -9,7 +9,7 @@ permissions:
contents: write

jobs:
build:
ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -27,5 +27,27 @@ jobs:
uses: softprops/action-gh-release@v3
with:
files: |
build/Milliways-unsigned.ipa
build/Milliways-simulator.zip
ios/build/Milliways-unsigned.ipa
ios/build/Milliways-simulator.zip

android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Set up JDK 22
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '22'

- name: Make gradlew executable
run: chmod +x android/gradlew

- name: Build debug APK
run: make apk

- name: Upload artifact to release
uses: softprops/action-gh-release@v3
with:
files: android/app/build/outputs/apk/debug/app-debug.apk
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: actions/upload-artifact@v5
with:
name: ipa-build
path: build/Milliways-unsigned.ipa
path: ios/build/Milliways-unsigned.ipa

test:
runs-on: ubuntu-latest
Expand All @@ -40,7 +40,7 @@ jobs:
uses: actions/download-artifact@v5
with:
name: ipa-build
path: build
path: ios/build

- name: Run tests using mobilewright
working-directory: tests
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
.PHONY: all zip ipa clean
.PHONY: all zip ipa apk clean

all: zip ipa
all: zip ipa apk

zip:
$(MAKE) -C ios zip

ipa:
$(MAKE) -C ios ipa

apk:
$(MAKE) -C android build

clean:
$(MAKE) -C ios clean
$(MAKE) -C android clean
15 changes: 15 additions & 0 deletions android/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
GRADLEW = ./gradlew
APK_PATH = app/build/outputs/apk/debug/app-debug.apk

.PHONY: build lint clean

build:
$(GRADLEW) assembleDebug
@echo "Created $(APK_PATH)"

lint:
$(GRADLEW) lint
@echo "Lint report: app/build/reports/lint-results-debug.html"

clean:
$(GRADLEW) clean
2 changes: 2 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ dependencies {
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.navigation.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
3 changes: 2 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Milliways">
android:theme="@style/Theme.Milliways"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
127 changes: 99 additions & 28 deletions android/app/src/main/java/com/mobilenext/Milliways/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,115 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.runtime.remember
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.mobilenext.Milliways.model.OrderManager
import com.mobilenext.Milliways.ui.screens.DeliveryScreen
import com.mobilenext.Milliways.ui.screens.MenuScreen
import com.mobilenext.Milliways.ui.screens.OrderScreen
import com.mobilenext.Milliways.ui.screens.WelcomeScreen
import com.mobilenext.Milliways.ui.theme.MilliwaysTheme

private const val ROUTE_WELCOME = "welcome"
private const val ROUTE_MENU = "menu"
private const val ROUTE_ORDER = "order"
private const val ROUTE_DELIVERY = "delivery"

private const val TRANSITION_MS = 350

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MilliwaysTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
val navController = rememberNavController()
val orderManager = remember { OrderManager() }

NavHost(
navController = navController,
startDestination = ROUTE_WELCOME,
// iOS-style push: new screen slides in from the right, current
// screen slides out partway to the left (and reverses on pop).
enterTransition = {
slideInHorizontally(tween(TRANSITION_MS)) { it }
},
exitTransition = {
slideOutHorizontally(tween(TRANSITION_MS)) { -it / 3 }
},
popEnterTransition = {
slideInHorizontally(tween(TRANSITION_MS)) { -it / 3 }
},
popExitTransition = {
slideOutHorizontally(tween(TRANSITION_MS)) { it }
}
) {
composable(
ROUTE_WELCOME,
// Closing Delivery pops straight back to Welcome (skipping Menu/Order) —
// let the cover's own slide-down read as the transition, Welcome just stays put.
popEnterTransition = {
if (initialState.destination.route == ROUTE_DELIVERY) {
EnterTransition.None
} else {
slideInHorizontally(tween(TRANSITION_MS)) { -it / 3 }
}
}
) {
WelcomeScreen(
orderManager = orderManager,
onNewOrder = { navController.navigate(ROUTE_MENU) }
)
}
composable(ROUTE_MENU) {
MenuScreen(
orderManager = orderManager,
onBack = { navController.popBackStack() },
onViewOrder = { navController.navigate(ROUTE_ORDER) }
)
}
composable(
ROUTE_ORDER,
// Order stays static underneath while Delivery slides up as a cover.
exitTransition = { ExitTransition.None }
) {
OrderScreen(
orderManager = orderManager,
onBack = { navController.popBackStack() },
onPlaceOrder = { navController.navigate(ROUTE_DELIVERY) }
)
}
composable(
ROUTE_DELIVERY,
// iOS fullScreenCover: slides up from the bottom over the
// current screen, and slides back down on close.
enterTransition = {
slideInVertically(tween(TRANSITION_MS)) { it }
},
exitTransition = { ExitTransition.None },
popExitTransition = {
slideOutVertically(tween(TRANSITION_MS)) { it }
}
) {
DeliveryScreen(
orderManager = orderManager,
onClose = {
orderManager.clearOrder()
navController.popBackStack(ROUTE_WELCOME, inclusive = false)
}
)
}
}
}
}
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MilliwaysTheme {
Greeting("Android")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mobilenext.Milliways.model

import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.Color
import java.util.UUID

data class MenuItem(
val id: String = UUID.randomUUID().toString(),
val name: String,
val description: String,
val price: Double,
val color: Color,
@DrawableRes val imageRes: Int? = null
)

fun formatPrice(value: Double): String = "₭%.2f".format(value)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.mobilenext.Milliways.model

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.mutableStateListOf
import java.util.UUID

data class OrderItem(
val menuItem: MenuItem,
var quantity: Int,
val id: String = UUID.randomUUID().toString()
) {
val totalPrice: Double get() = quantity * menuItem.price
}

class OrderManager {
val items: SnapshotStateList<OrderItem> = mutableStateListOf()
var couponDiscount by mutableStateOf(0.0)
private set
var appliedCouponCode: String? by mutableStateOf(null)
private set

val totalPrice: Double get() = items.sumOf { it.totalPrice }

// Coupon discount is frozen when applied — does not update if cart changes
val finalTotal: Double get() = totalPrice - couponDiscount

val totalQuantity: Int get() = items.sumOf { it.quantity }

fun addItem(item: MenuItem, quantity: Int) {
items.add(OrderItem(item, quantity))
}

fun removeItem(id: String) {
items.removeAll { it.id == id }
}

fun applyCoupon(code: String): Boolean {
if (!code.equals("MARVIN", ignoreCase = true)) return false
couponDiscount = 20.0
appliedCouponCode = "MARVIN"
return true
}

fun clearOrder() {
items.clear()
couponDiscount = 0.0
appliedCouponCode = null
}
}
Loading
Loading