Skip to content

Commit

Permalink
feat: Implemented server photo storage and client fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
oltimaloku committed May 11, 2024
1 parent f0931a8 commit c5d49cb
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 80 deletions.
43 changes: 43 additions & 0 deletions client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
29 changes: 7 additions & 22 deletions client/.metadata
Original file line number Diff line number Diff line change
@@ -1,38 +1,23 @@
# 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: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
revision: "54e66469a933b60ddf175f858f82eaeb97e48c8d"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: android
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
- platform: ios
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: linux
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: macos
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: web
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: windows
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d

# User provided section

Expand Down
16 changes: 16 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# client

A new Flutter project.

## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
36 changes: 32 additions & 4 deletions client/lib/features/gallery/services/gallery_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';

import 'package:georeal/constants/env_variables.dart';
Expand All @@ -6,23 +8,49 @@ import 'package:http/http.dart' as http;
/// Handles http requests for the Gallery
class GalleryService {
static Future<void> uploadPhoto(String geoSphereId, File photo) async {
var uri = Uri.parse('${EnvVariables.uri}/geofences');
static Future<void> uploadPhoto(
int geoSphereId, int userId, File photo) async {
var uri =
Uri.parse('${EnvVariables.uri}/geofences/$geoSphereId/upload_photo');

var request = http.MultipartRequest('POST', uri)
..fields['author_id'] = userId.toString()
..files.add(await http.MultipartFile.fromPath('photo', photo.path));

try {
var streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);

if (response.statusCode == 200) {
print('Photo uploaded successfully');
log('Photo uploaded successfully');
} else if (response.statusCode == 404) {
log('Geofence not found');
} else if (response.statusCode == 400) {
log('No valid files were uploaded');
} else {
print('Failed to upload photo. Status code: ${response.statusCode}');
log('Failed to upload photo. Status code: ${response.statusCode}');
}
} catch (e) {
throw Exception("Error occurred: $e");
}
}

static Future<List<String>> getPhotosByGeoSphereId(int geoSphereId) async {
var response = await http
.get(Uri.parse('${EnvVariables.uri}/geofences/$geoSphereId/posts'));

if (response.statusCode == 200) {
List<dynamic> photosData = json.decode(response.body);
List<String> photoUrls = photosData
.map((photoData) =>
"${EnvVariables.uri}/uploads/${photoData['photo_url']}" // Ensure the server gives the full path or append domain if necessary
)
.toList();
log("photoUrls: $photoUrls");
return photoUrls;
} else {
throw Exception(
'Failed to get photos. Status code: ${response.statusCode}');
}
}
}
39 changes: 34 additions & 5 deletions client/lib/features/gallery/view_model/gallery_view_model.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:georeal/features/gallery/services/gallery_service.dart';

import '../../../models/gallery_model.dart';

Expand All @@ -12,15 +15,41 @@ class GalleryViewModel extends ChangeNotifier {
notifyListeners();
}

void addPhotoToGallery(int geoSphereId, String photoPath) {
// void addPhotoToGallery(int geoSphereId, String photoPath) {
// final gallery = _galleries[geoSphereId];
// if (gallery != null) {
// gallery.photoPaths.add(photoPath);
// notifyListeners();
// } else {
// Gallery newGallery = Gallery(id: geoSphereId);
// newGallery.photoPaths.add(photoPath);
// _galleries[geoSphereId] = newGallery;
// notifyListeners();
// }
// }

Future<void> addPhotoToGallery(
int geoSphereID, File photo, int userID) async {
await GalleryService.uploadPhoto(geoSphereID, userID, photo);
}

Future<void> fetchGallery(int geoSphereId) async {
List<String>? photoUrls =
await GalleryService.getPhotosByGeoSphereId(geoSphereId);

if (photoUrls == null || photoUrls.isEmpty) {
// Optionally handle the case where no photos are found
}

final gallery = _galleries[geoSphereId];

if (gallery != null) {
gallery.photoPaths.add(photoPath);
// Update existing gallery
gallery.photoPaths = photoUrls;
notifyListeners();
} else {
Gallery newGallery = Gallery(id: geoSphereId);
newGallery.photoPaths.add(photoPath);
_galleries[geoSphereId] = newGallery;
// Create a new gallery if one doesn't exist
_galleries[geoSphereId] = Gallery(id: geoSphereId, photoPaths: photoUrls);
notifyListeners();
}
}
Expand Down
44 changes: 28 additions & 16 deletions client/lib/features/gallery/views/geo_sphere_gallery.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import 'dart:io';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:georeal/features/gallery/view_model/gallery_view_model.dart';
import 'package:georeal/features/gallery/widgets/gallery_navbar.dart';
import 'package:georeal/global_variables.dart';
import 'package:georeal/models/geo_sphere_model.dart';
import 'package:provider/provider.dart';

/// Gallery view for a specific GeoSphere

class GeoSphereGallery extends StatelessWidget {
final GeoSphere geoSphere;
Expand All @@ -20,7 +17,7 @@ class GeoSphereGallery extends StatelessWidget {
@override
Widget build(BuildContext context) {
final galleryViewModel = context.watch<GalleryViewModel>();
List<String> photoPaths =
List<String> photoUrls =
galleryViewModel.getPhotosFromGallery(geoSphere.geoSphereId);

return Scaffold(
Expand All @@ -31,25 +28,40 @@ class GeoSphereGallery extends StatelessWidget {
GalleryNavBar(
geoSphere: geoSphere,
),
ElevatedButton(
onPressed: () {
galleryViewModel.fetchGallery(geoSphere.geoSphereId);
},
child: Text("Fetch Gallery")),
Expanded(
// Wrap ListView.builder with Expanded
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 0,
mainAxisSpacing: 6,
),
itemCount: photoPaths.length,
itemCount: photoUrls.length,
itemBuilder: (context, index) {
String photoPath = photoPaths[index];
String photoUrl = photoUrls[index];
return GestureDetector(
child: Image.file(
File(photoPath),
child: Image.network(
photoUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
Icon(Icons.error),
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
));
},
),
onTap: () => _showFullSizeImage(context, photoPath),
onTap: () => _showFullSizeImage(context, photoUrl),
);
/*Image.asset(
photoPath); */ // Display the image (assuming these are network images)
},
),
),
Expand All @@ -59,12 +71,12 @@ class GeoSphereGallery extends StatelessWidget {
);
}

_showFullSizeImage(BuildContext context, String photoPath) {
_showFullSizeImage(BuildContext context, String photoUrl) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Image.file(File(photoPath)),
content: Image.network(photoUrl),
actions: <Widget>[
TextButton(
child: const Text('Close'),
Expand Down
13 changes: 8 additions & 5 deletions client/lib/features/gallery/widgets/photo_prompt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:georeal/features/gallery/view_model/gallery_view_model.dart';
import 'package:georeal/providers/user_provider';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
Expand All @@ -25,7 +26,7 @@ class _PhotoPromptState extends State<PhotoPrompt> {
File? image;

Future pickImage(
ImageSource source, GalleryViewModel galleryViewModel) async {
ImageSource source, GalleryViewModel galleryViewModel, int userID) async {
try {
final image = await ImagePicker().pickImage(source: source);
if (image == null) return;
Expand All @@ -35,7 +36,7 @@ class _PhotoPromptState extends State<PhotoPrompt> {
log("beofre");
// Save the image path to the gallery
galleryViewModel.addPhotoToGallery(
widget.geosphere.geoSphereId, image.path);
widget.geosphere.geoSphereId, imageTemporary, userID);
log("after");
/*
Provider.of<GalleryService>(context, listen: false)
Expand All @@ -54,28 +55,30 @@ class _PhotoPromptState extends State<PhotoPrompt> {
Widget build(BuildContext context) {
final galleryViewModel =
Provider.of<GalleryViewModel>(context, listen: false);
final userID = Provider.of<UserProvider>(context).user!.id;
return AlertDialog(
title: const Text("Add a photo!"),
content: SizedBox(
height: 200,
child: Column(
children: [
ElevatedButton(
onPressed: () => pickImage(ImageSource.gallery, galleryViewModel),
onPressed: () =>
pickImage(ImageSource.gallery, galleryViewModel, userID),
child: const Text("Pick Gallery"),
),
ElevatedButton(
onPressed: () async {
if (Platform.isAndroid) {
var status = await Permission.camera.request();
if (status.isGranted) {
pickImage(ImageSource.camera, galleryViewModel);
pickImage(ImageSource.camera, galleryViewModel, userID);
} else {
print("bruh");
}
} else if (Platform.isIOS) {
// TODO: Implement iOS camera permission
pickImage(ImageSource.camera, galleryViewModel);
pickImage(ImageSource.camera, galleryViewModel, userID);
}
},
child: const Text("Pick Camera"),
Expand Down
14 changes: 14 additions & 0 deletions client/lib/features/geo_sphere/services/geo_sphere_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,18 @@ class GeoSphereService {
throw Exception("Error occurred: $e");
}
}

static Future<void> deleteGeoSphere(int id) async {
log('Deleting geosphere with id: $id');
try {
var response = await http
.delete(Uri.parse('${EnvVariables.uri}/geofences/delete/$id'));
if (response.statusCode != 200) {
throw Exception(
'Failed to delete geosphere. Status code: ${response.statusCode}');
}
} catch (e) {
throw Exception("Error occurred: $e");
}
}
}
Loading

0 comments on commit c5d49cb

Please sign in to comment.