Build iOS and Android apps for Canada's growing mobile market
Duration: 16-20 weeks (self-paced)
Level: Beginner to Advanced
Prerequisites: Basic programming knowledge, preferably JavaScript or Swift
import SwiftUI
import CoreLocation
// Toronto Weather App using SwiftUI
struct TorontoWeatherApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@StateObject private var weatherManager = WeatherManager()
@State private var showingAlert = false
var body: some View {
NavigationView {
ZStack {
// Toronto-themed background
LinearGradient(
colors: [Color.blue, Color.gray.opacity(0.3)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 20) {
// Toronto City Header
HStack {
Image(systemName: "location.fill")
.foregroundColor(.white)
Text("Toronto, ON")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
}
// Weather Information
if let weather = weatherManager.currentWeather {
VStack(spacing: 10) {
Text("\(weather.temperature)Β°C")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
Text(weather.description)
.font(.headline)
.foregroundColor(.white.opacity(0.9))
HStack(spacing: 30) {
WeatherDetailView(
icon: "humidity.fill",
title: "Humidity",
value: "\(weather.humidity)%"
)
WeatherDetailView(
icon: "wind",
title: "Wind",
value: "\(weather.windSpeed) km/h"
)
WeatherDetailView(
icon: "thermometer",
title: "Feels Like",
value: "\(weather.feelsLike)Β°C"
)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 20)
.fill(.ultraThinMaterial)
)
} else {
ProgressView("Loading Toronto weather...")
.foregroundColor(.white)
}
// TTC Integration Button
Button("Check TTC Delays") {
weatherManager.checkTTCStatus()
showingAlert = true
}
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
Spacer()
}
.padding()
}
}
.onAppear {
weatherManager.requestLocation()
}
.alert("TTC Status", isPresented: $showingAlert) {
Button("OK") { }
} message: {
Text("TTC services are running normally on all lines.")
}
}
}
struct WeatherDetailView: View {
let icon: String
let title: String
let value: String
var body: some View {
VStack {
Image(systemName: icon)
.font(.title2)
.foregroundColor(.white)
Text(title)
.font(.caption)
.foregroundColor(.white.opacity(0.8))
Text(value)
.font(.headline)
.foregroundColor(.white)
}
}
}
// Weather Manager for Toronto data
class WeatherManager: ObservableObject {
@Published var currentWeather: WeatherData?
private let locationManager = CLLocationManager()
struct WeatherData {
let temperature: Int
let description: String
let humidity: Int
let windSpeed: Int
let feelsLike: Int
}
init() {
setupLocationManager()
}
private func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func requestLocation() {
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
func fetchWeatherData(for location: CLLocation) {
// Integration with Environment Canada API
let weatherAPIURL = "https://api.weather.gc.ca/forecast/Toronto"
// Mock data for tutorial - in real app, use URLSession
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.currentWeather = WeatherData(
temperature: -2,
description: "Light Snow",
humidity: 78,
windSpeed: 15,
feelsLike: -8
)
}
}
func checkTTCStatus() {
// Integration with TTC API for service alerts
// In real app, would fetch from TTC's real-time API
}
}
extension WeatherManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
fetchWeatherData(for: location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failed to get location: \(error)")
}
}
Toronto Transit Tracker - Build a comprehensive iOS app that tracks TTC buses, subways, and GO Transit with real-time delays, route planning, and offline maps.
// Toronto Food Delivery App using Jetpack Compose
package com.torontofood.app
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
// Data models for Toronto restaurants
data class Restaurant(
val id: String,
val name: String,
val cuisine: String,
val neighborhood: TorontoNeighborhood,
val rating: Float,
val deliveryTime: Int,
val deliveryFee: Double,
val isOpen: Boolean,
val specialties: List
)
enum class TorontoNeighborhood(val displayName: String) {
DOWNTOWN("Downtown Toronto"),
NORTH_YORK("North York"),
SCARBOROUGH("Scarborough"),
ETOBICOKE("Etobicoke"),
EAST_YORK("East York"),
YORKVILLE("Yorkville"),
DISTILLERY("Distillery District"),
LIBERTY_VILLAGE("Liberty Village"),
KING_WEST("King West"),
FINANCIAL_DISTRICT("Financial District")
}
// ViewModel for managing restaurant data
class TorontoFoodViewModel : ViewModel() {
private val _restaurants = mutableStateOf(getSampleTorontoRestaurants())
val restaurants: State> = _restaurants
private val _selectedNeighborhood = mutableStateOf(null)
val selectedNeighborhood: State = _selectedNeighborhood
fun filterByNeighborhood(neighborhood: TorontoNeighborhood?) {
_selectedNeighborhood.value = neighborhood
}
fun getFilteredRestaurants(): List {
return if (_selectedNeighborhood.value == null) {
_restaurants.value
} else {
_restaurants.value.filter { it.neighborhood == _selectedNeighborhood.value }
}
}
private fun getSampleTorontoRestaurants(): List {
return listOf(
Restaurant(
id = "1",
name = "The Keg King St",
cuisine = "Canadian Steakhouse",
neighborhood = TorontoNeighborhood.FINANCIAL_DISTRICT,
rating = 4.5f,
deliveryTime = 35,
deliveryFee = 3.99,
isOpen = true,
specialties = listOf("AAA Steaks", "Canadian Wine", "Poutine")
),
Restaurant(
id = "2",
name = "Pai Northern Thai Kitchen",
cuisine = "Thai",
neighborhood = TorontoNeighborhood.DOWNTOWN,
rating = 4.7f,
deliveryTime = 25,
deliveryFee = 2.99,
isOpen = true,
specialties = listOf("Pad Thai", "Green Curry", "Mango Sticky Rice")
),
Restaurant(
id = "3",
name = "St. Lawrence Market Peameal",
cuisine = "Canadian Deli",
neighborhood = TorontoNeighborhood.DOWNTOWN,
rating = 4.8f,
deliveryTime = 20,
deliveryFee = 1.99,
isOpen = true,
specialties = listOf("Peameal Bacon Sandwich", "Butter Tarts", "Tourtière")
)
)
}
}
// Main Composable Screen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TorontoFoodDeliveryScreen(
viewModel: TorontoFoodViewModel = viewModel()
) {
val restaurants = viewModel.getFilteredRestaurants()
val selectedNeighborhood by viewModel.selectedNeighborhood
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Toronto-themed header
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF003366) // Toronto blue
)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "π Toronto Food Delivery",
style = MaterialTheme.typography.headlineMedium,
color = Color.White
)
Text(
text = "Discover the best of Toronto cuisine",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(alpha = 0.9f)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Neighborhood filter
NeighborhoodFilter(
selectedNeighborhood = selectedNeighborhood,
onNeighborhoodSelected = { viewModel.filterByNeighborhood(it) }
)
Spacer(modifier = Modifier.height(16.dp))
// Restaurant list
LazyColumn(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(restaurants) { restaurant ->
RestaurantCard(restaurant = restaurant)
}
}
}
}
@Composable
fun NeighborhoodFilter(
selectedNeighborhood: TorontoNeighborhood?,
onNeighborhoodSelected: (TorontoNeighborhood?) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
OutlinedTextField(
value = selectedNeighborhood?.displayName ?: "All Toronto",
onValueChange = { },
readOnly = true,
label = { Text("Filter by Neighborhood") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text("All Toronto") },
onClick = {
onNeighborhoodSelected(null)
expanded = false
}
)
TorontoNeighborhood.values().forEach { neighborhood ->
DropdownMenuItem(
text = { Text(neighborhood.displayName) },
onClick = {
onNeighborhoodSelected(neighborhood)
expanded = false
}
)
}
}
}
}
@Composable
fun RestaurantCard(restaurant: Restaurant) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = restaurant.name,
style = MaterialTheme.typography.titleMedium
)
Text(
text = restaurant.cuisine,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = restaurant.neighborhood.displayName,
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF003366) // Toronto blue
)
}
Column(horizontalAlignment = Alignment.End) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "β ${restaurant.rating}",
style = MaterialTheme.typography.bodyMedium
)
}
Text(
text = "${restaurant.deliveryTime} min β’ $${restaurant.deliveryFee}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
if (restaurant.specialties.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Specialties: ${restaurant.specialties.joinToString(", ")}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(modifier = Modifier.height(12.dp))
Button(
onClick = { /* Navigate to restaurant details */ },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = if (restaurant.isOpen) Color(0xFFD32F2F) else Color.Gray
)
) {
Text(
text = if (restaurant.isOpen) "Order Now" else "Currently Closed"
)
}
}
}
}
Toronto Events Discovery App - Create an Android app that aggregates events from across Toronto, integrates with Ticketmaster, and provides location-based recommendations.
// Toronto Real Estate App - React Native
import React, { useState, useEffect } from 'react';
import {
View,
Text,
FlatList,
TouchableOpacity,
StyleSheet,
Image,
Alert,
Platform
} from 'react-native';
import MapView, { Marker } from 'react-native-maps';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
// Toronto Real Estate Data Model
const torontoNeighborhoods = [
{
id: '1',
name: 'Downtown Toronto',
averagePrice: 895000,
pricePerSqFt: 1250,
coordinates: { latitude: 43.6532, longitude: -79.3832 },
trending: 'up',
properties: [
{
id: '101',
address: '123 King St W',
price: 750000,
bedrooms: 1,
bathrooms: 1,
sqft: 600,
type: 'Condo',
images: ['https://example.com/condo1.jpg'],
description: 'Modern downtown condo with CN Tower views'
},
{
id: '102',
address: '456 Queen St E',
price: 1200000,
bedrooms: 2,
bathrooms: 2,
sqft: 950,
type: 'Condo',
images: ['https://example.com/condo2.jpg'],
description: 'Luxury condo in the heart of Toronto'
}
]
},
{
id: '2',
name: 'North York',
averagePrice: 725000,
pricePerSqFt: 850,
coordinates: { latitude: 43.7615, longitude: -79.4111 },
trending: 'stable',
properties: [
{
id: '201',
address: '789 Yonge St',
price: 680000,
bedrooms: 3,
bathrooms: 2,
sqft: 1200,
type: 'Townhouse',
images: ['https://example.com/townhouse1.jpg'],
description: 'Family-friendly townhouse near TTC'
}
]
}
];
// Main App Component
const TorontoRealEstateApp = () => {
const Stack = createStackNavigator();
return (
({ title: route.params.neighborhoodName })}
/>
);
};
// Neighborhood List Screen
const NeighborhoodListScreen = ({ navigation }) => {
const [neighborhoods, setNeighborhoods] = useState(torontoNeighborhoods);
const renderNeighborhood = ({ item }) => (
navigation.navigate('PropertyList', {
neighborhood: item,
neighborhoodName: item.name
})}
>
{item.name}
{item.trending === 'up' ? 'π' : 'π'} {item.trending.toUpperCase()}
Average: ${item.averagePrice.toLocaleString()}
${item.pricePerSqFt}/sq ft
{item.properties.length} properties available
);
return (
Toronto Neighborhoods
navigation.navigate('MapView')}
>
πΊοΈ Map View
item.id}
showsVerticalScrollIndicator={false}
/>
);
};
// Property List Screen
const PropertyListScreen = ({ route, navigation }) => {
const { neighborhood } = route.params;
const [properties, setProperties] = useState(neighborhood.properties);
const renderProperty = ({ item }) => (
navigation.navigate('PropertyDetails', { property: item })}
>
π
{item.address}
${item.price.toLocaleString()}
ποΈ {item.bedrooms} bed
πΏ {item.bathrooms} bath
π {item.sqft} sq ft
{item.type}
);
return (
Properties in {neighborhood.name}
Average: ${neighborhood.averagePrice.toLocaleString()} β’
${neighborhood.pricePerSqFt}/sq ft
item.id}
showsVerticalScrollIndicator={false}
/>
);
};
// Toronto Map View Screen
const MapViewScreen = () => {
const [selectedProperty, setSelectedProperty] = useState(null);
const allProperties = torontoNeighborhoods.flatMap(neighborhood =>
neighborhood.properties.map(property => ({
...property,
neighborhood: neighborhood.name,
coordinates: neighborhood.coordinates
}))
);
return (
{torontoNeighborhoods.map(neighborhood => (
))}
{selectedProperty && (
{selectedProperty.address}
${selectedProperty.price.toLocaleString()}
)}
);
};
// Property Details Screen
const PropertyDetailsScreen = ({ route }) => {
const { property } = route.params;
const handleContactAgent = () => {
Alert.alert(
'Contact Agent',
'Would you like to contact the listing agent?',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Call', onPress: () => console.log('Calling agent...') },
{ text: 'Email', onPress: () => console.log('Emailing agent...') }
]
);
};
return (
{property.address}
${property.price.toLocaleString()}
Bedrooms
{property.bedrooms}
Bathrooms
{property.bathrooms}
Square Feet
{property.sqft}
Property Type
{property.type}
Description
{property.description}
Contact Agent
);
};
// Helper Functions
const getTrendingColor = (trending) => {
switch (trending) {
case 'up': return '#4CAF50';
case 'down': return '#F44336';
default: return '#FF9800';
}
};
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
headerTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#003366',
},
mapButton: {
backgroundColor: '#003366',
padding: 8,
borderRadius: 6,
},
mapButtonText: {
color: '#fff',
fontWeight: 'bold',
},
neighborhoodCard: {
backgroundColor: '#fff',
margin: 12,
padding: 16,
borderRadius: 8,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
neighborhoodName: {
fontSize: 18,
fontWeight: 'bold',
color: '#003366',
},
trendingIndicator: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
},
trendingText: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
},
priceInfo: {
marginBottom: 8,
},
averagePrice: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
},
pricePerSqFt: {
fontSize: 14,
color: '#666',
},
propertyCount: {
fontSize: 14,
color: '#666',
fontStyle: 'italic',
},
mapContainer: {
flex: 1,
},
map: {
flex: 1,
},
// Additional styles...
});
export default TorontoRealEstateApp;
Toronto Fitness Community App - Build a React Native app connecting fitness enthusiasts across Toronto with gym finder, workout tracking, and social features.
// Toronto Bike Share App - Flutter
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
runApp(TorontoBikeShareApp());
}
class TorontoBikeShareApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Toronto Bike Share',
theme: ThemeData(
primaryColor: Color(0xFF003366), // Toronto blue
accentColor: Color(0xFFD32F2F), // Toronto red
fontFamily: 'Roboto',
),
home: BikeShareHomeScreen(),
);
}
}
// Data Models
class BikeStation {
final String id;
final String name;
final double latitude;
final double longitude;
final int availableBikes;
final int availableDocks;
final bool isActive;
final String address;
final String neighborhood;
BikeStation({
required this.id,
required this.name,
required this.latitude,
required this.longitude,
required this.availableBikes,
required this.availableDocks,
required this.isActive,
required this.address,
required this.neighborhood,
});
factory BikeStation.fromJson(Map json) {
return BikeStation(
id: json['station_id'],
name: json['name'],
latitude: json['lat'].toDouble(),
longitude: json['lon'].toDouble(),
availableBikes: json['num_bikes_available'],
availableDocks: json['num_docks_available'],
isActive: json['is_active'] == 1,
address: json['address'] ?? '',
neighborhood: json['neighborhood'] ?? 'Toronto',
);
}
}
class BikeTrip {
final String id;
final BikeStation startStation;
final BikeStation? endStation;
final DateTime startTime;
final DateTime? endTime;
final double? distance;
final int? duration;
BikeTrip({
required this.id,
required this.startStation,
this.endStation,
required this.startTime,
this.endTime,
this.distance,
this.duration,
});
}
// Main Home Screen
class BikeShareHomeScreen extends StatefulWidget {
@override
_BikeShareHomeScreenState createState() => _BikeShareHomeScreenState();
}
class _BikeShareHomeScreenState extends State
with SingleTickerProviderStateMixin {
late TabController _tabController;
List _stations = [];
BikeStation? _nearestStation;
LocationData? _currentLocation;
bool _isLoading = true;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_loadBikeStations();
_getCurrentLocation();
}
Future _loadBikeStations() async {
try {
// In real app, would use Toronto Bike Share API
// For demo, using sample data
setState(() {
_stations = _getSampleTorontoStations();
_isLoading = false;
});
} catch (e) {
print('Error loading stations: $e');
setState(() {
_isLoading = false;
});
}
}
Future _getCurrentLocation() async {
Location location = Location();
bool serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) return;
}
PermissionStatus permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) return;
}
LocationData locationData = await location.getLocation();
setState(() {
_currentLocation = locationData;
_findNearestStation();
});
}
void _findNearestStation() {
if (_currentLocation == null || _stations.isEmpty) return;
double minDistance = double.infinity;
BikeStation? nearest;
for (BikeStation station in _stations) {
double distance = _calculateDistance(
_currentLocation!.latitude!,
_currentLocation!.longitude!,
station.latitude,
station.longitude,
);
if (distance < minDistance && station.isActive && station.availableBikes > 0) {
minDistance = distance;
nearest = station;
}
}
setState(() {
_nearestStation = nearest;
});
}
double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {
// Haversine formula for distance calculation
const double earthRadius = 6371; // Earth's radius in kilometers
double dLat = _degreeToRadian(lat2 - lat1);
double dLon = _degreeToRadian(lon2 - lon1);
double a = math.sin(dLat / 2) * math.sin(dLat / 2) +
math.cos(_degreeToRadian(lat1)) * math.cos(_degreeToRadian(lat2)) *
math.sin(dLon / 2) * math.sin(dLon / 2);
double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
return earthRadius * c;
}
double _degreeToRadian(double degree) {
return degree * (math.pi / 180);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Icon(Icons.directions_bike, color: Colors.white),
SizedBox(width: 8),
Text('Toronto Bike Share',
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
backgroundColor: Theme.of(context).primaryColor,
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.map), text: 'Map'),
Tab(icon: Icon(Icons.list), text: 'Stations'),
Tab(icon: Icon(Icons.history), text: 'My Trips'),
],
),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: TabBarView(
controller: _tabController,
children: [
BikeStationMapView(stations: _stations, currentLocation: _currentLocation),
BikeStationListView(stations: _stations, nearestStation: _nearestStation),
MyTripsView(),
],
),
floatingActionButton: _nearestStation != null
? FloatingActionButton.extended(
onPressed: () => _startBikeRental(_nearestStation!),
icon: Icon(Icons.play_arrow),
label: Text('Start Ride'),
backgroundColor: Theme.of(context).accentColor,
)
: null,
);
}
void _startBikeRental(BikeStation station) {
showModalBottomSheet(
context: context,
builder: (context) => BikeRentalSheet(station: station),
);
}
List _getSampleTorontoStations() {
return [
BikeStation(
id: '1',
name: 'Union Station',
latitude: 43.6452,
longitude: -79.3806,
availableBikes: 12,
availableDocks: 8,
isActive: true,
address: '65 Front St W, Toronto',
neighborhood: 'Financial District',
),
BikeStation(
id: '2',
name: 'Harbourfront Centre',
latitude: 43.6387,
longitude: -79.3816,
availableBikes: 8,
availableDocks: 12,
isActive: true,
address: '235 Queens Quay W, Toronto',
neighborhood: 'Harbourfront',
),
BikeStation(
id: '3',
name: 'CN Tower',
latitude: 43.6426,
longitude: -79.3871,
availableBikes: 15,
availableDocks: 5,
isActive: true,
address: '290 Bremner Blvd, Toronto',
neighborhood: 'Entertainment District',
),
// Add more sample stations...
];
}
}
// Bike Station List View
class BikeStationListView extends StatelessWidget {
final List stations;
final BikeStation? nearestStation;
const BikeStationListView({
Key? key,
required this.stations,
this.nearestStation,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(8),
itemCount: stations.length,
itemBuilder: (context, index) {
BikeStation station = stations[index];
bool isNearest = nearestStation?.id == station.id;
return Card(
elevation: isNearest ? 4 : 2,
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: station.isActive
? (station.availableBikes > 0 ? Colors.green : Colors.orange)
: Colors.grey,
child: Text(
'${station.availableBikes}',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
title: Row(
children: [
Expanded(
child: Text(
station.name,
style: TextStyle(
fontWeight: isNearest ? FontWeight.bold : FontWeight.normal,
),
),
),
if (isNearest)
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'NEAREST',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(station.address),
SizedBox(height: 4),
Row(
children: [
Icon(Icons.directions_bike, size: 16, color: Colors.green),
Text(' ${station.availableBikes} bikes'),
SizedBox(width: 16),
Icon(Icons.lock_open, size: 16, color: Colors.blue),
Text(' ${station.availableDocks} docks'),
],
),
],
),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StationDetailScreen(station: station),
),
);
},
),
);
},
);
}
}
// Bike Rental Bottom Sheet
class BikeRentalSheet extends StatefulWidget {
final BikeStation station;
const BikeRentalSheet({Key? key, required this.station}) : super(key: key);
@override
_BikeRentalSheetState createState() => _BikeRentalSheetState();
}
class _BikeRentalSheetState extends State {
bool _isRenting = false;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Rent a Bike',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 16),
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.station.name,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(widget.station.address),
SizedBox(height: 8),
Row(
children: [
Icon(Icons.directions_bike, color: Colors.green),
Text(' ${widget.station.availableBikes} bikes available'),
],
),
],
),
),
),
SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isRenting ? null : _startRental,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).accentColor,
padding: EdgeInsets.symmetric(vertical: 16),
),
child: _isRenting
? CircularProgressIndicator(color: Colors.white)
: Text(
'Start Rental - \$1.15 + \$0.12/min',
style: TextStyle(fontSize: 16),
),
),
),
SizedBox(height: 16),
],
),
);
}
Future _startRental() async {
setState(() {
_isRenting = true;
});
// Simulate rental process
await Future.delayed(Duration(seconds: 2));
setState(() {
_isRenting = false;
});
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Bike rental started! Enjoy your ride in Toronto!'),
backgroundColor: Colors.green,
),
);
}
}
// Add math import at the top
import 'dart:math' as math;
Toronto Language Exchange App - Create a Flutter app connecting language learners across Toronto's multicultural communities with chat, meetup organization, and progress tracking.
App Store Presence: Publish 2-3 polished apps on both iOS App Store and Google Play Store showcasing different technologies and Toronto market needs.
Technical Skills Showcase: Demonstrate API integration, offline functionality, push notifications, payment processing, and accessibility features in your apps.
Join Toronto's thriving mobile development community and create apps that serve millions of Canadian users.