diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png
index 15668959..ef8b1932 100644
Binary files a/android/app/src/main/res/drawable-hdpi/splash.png and b/android/app/src/main/res/drawable-hdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png
index e04417ac..dc3eea0d 100644
Binary files a/android/app/src/main/res/drawable-mdpi/splash.png and b/android/app/src/main/res/drawable-mdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png
index 139dd5e2..28d5e57e 100644
Binary files a/android/app/src/main/res/drawable-xhdpi/splash.png and b/android/app/src/main/res/drawable-xhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png
index d445b92a..a1996bf3 100644
Binary files a/android/app/src/main/res/drawable-xxhdpi/splash.png and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png
index b4c1874e..0d405700 100644
Binary files a/android/app/src/main/res/drawable-xxxhdpi/splash.png and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index d08fafc1..91c2d8b8 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png 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
index 2341e2ed..a146aea6 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png 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
index 06e8f99e..c3379ea5 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png 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
index 110971cf..6130a555 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png 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
index cf1ea232..31ac45f9 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/assets/images/ground_flip_app_icon.png b/assets/images/ground_flip_app_icon.png
new file mode 100644
index 00000000..bbb21a9b
Binary files /dev/null and b/assets/images/ground_flip_app_icon.png differ
diff --git a/flutter_launcher_icons.yaml b/flutter_launcher_icons.yaml
index 96eb549e..18b7ce68 100644
--- a/flutter_launcher_icons.yaml
+++ b/flutter_launcher_icons.yaml
@@ -1,5 +1,5 @@
flutter_icons:
ios: true
android: true
- image_path: "assets/images/app_icon.png"
+ image_path: "assets/images/ground_flip_app_icon.png"
remove_alpha_ios: true
\ No newline at end of file
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
index 316c49bf..46ade383 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png 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
index df2390b9..637db2ae 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png 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
index 6162c2cc..ed823268 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png 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
index 7a11ade0..e2fc3ab2 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png 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
index 869a5b77..639f1a2a 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png 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
index a965691a..af914455 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png 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
index 56f7ad58..94a25a26 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png 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
index 6162c2cc..ed823268 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png 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
index 5a6c0a47..2872fe58 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png 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
index 0431ba8c..4f3f027e 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png 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-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
index 8200c6df..a018a81b 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
index 9f4b4030..fd5819bf 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
index 858c49af..7ce65c8a 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
index e46bdfdd..296de1bc 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.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
index 0431ba8c..4f3f027e 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png 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
index d6dad8ac..566e3798 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png 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-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
index 1a62e602..dbbc9e23 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
index 688418a1..233d0404 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.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
index 4da0e146..991e0277 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png 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
index 7d06c3b4..3d0753c6 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png 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
index db40f313..6d68c2f7 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png 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/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
index e04417ac..dc3eea0d 100644
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png 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
index 139dd5e2..28d5e57e 100644
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png 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
index d445b92a..a1996bf3 100644
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
index 4cc69847..8d2b7d51 100644
--- a/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -38,7 +38,7 @@
-
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 44ac26de..78d87435 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -56,7 +56,7 @@
NSLocationAlwaysAndWhenInUseUsageDescription
This app needs GPS
NSLocationWhenInUseUsageDescription
- This app needs GPS
+ This app needs GPS
UIApplicationSupportsIndirectInputEvents
UILaunchStoryboardName
diff --git a/lib/constants/app_colors.dart b/lib/constants/app_colors.dart
index 80280fdb..80d01218 100644
--- a/lib/constants/app_colors.dart
+++ b/lib/constants/app_colors.dart
@@ -16,5 +16,5 @@ class AppColors {
static const Color boxColor = Color(0xFF1F1F1F);
static const Color boxColorSecond = Color(0xFF555555);
static const Color buttonColor = Colors.white;
- static const Color navigationBarColor = Color(0xA6212121);
+ static const Color navigationBarColor = Color(0xFF1D1D1D);
}
diff --git a/lib/constants/text_styles.dart b/lib/constants/text_styles.dart
index e8031857..2cab5abe 100644
--- a/lib/constants/text_styles.dart
+++ b/lib/constants/text_styles.dart
@@ -57,6 +57,12 @@ class TextStyles {
fontWeight: FontWeight.w500,
);
+ static TextStyle fs14w500cTextSecondary = TextStyle(
+ color: AppColors.textSecondary,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ );
+
static TextStyle fs14w400cTextSecondary = TextStyle(
color: AppColors.textSecondary,
fontSize: 14,
@@ -75,6 +81,12 @@ class TextStyles {
fontWeight: FontWeight.w800,
);
+ static TextStyle fs24w900cTextPrimary = TextStyle(
+ color: AppColors.textPrimary,
+ fontSize: 24,
+ fontWeight: FontWeight.w900,
+ );
+
static TextStyle fs32w400cTextSecondary = TextStyle(
color: AppColors.textSecondary,
fontSize: 32,
diff --git a/lib/controllers/bottom_sheet_controller.dart b/lib/controllers/bottom_sheet_controller.dart
new file mode 100644
index 00000000..7c6efb5c
--- /dev/null
+++ b/lib/controllers/bottom_sheet_controller.dart
@@ -0,0 +1,95 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../enums/pixel_mode.dart';
+import '../models/individual_history_pixel_info.dart';
+import '../models/individual_mode_pixel_info.dart';
+import '../service/pixel_service.dart';
+import '../widgets/map/bottom_sheet/individual_history_list.dart';
+import '../widgets/map/bottom_sheet/pixel_info_header.dart';
+import '../widgets/map/bottom_sheet/step_stats.dart';
+import '../widgets/map/bottom_sheet/visited_user_list.dart';
+
+class BottomSheetController extends GetxController {
+ final PixelService pixelService = PixelService();
+ final DraggableScrollableController draggableController =
+ DraggableScrollableController();
+ Widget currentBody = StepStatsBody();
+ Widget currentHeader = StepStats();
+ RxInt mode = 0.obs;
+ RxBool changeVar = true.obs;
+ double size = 1.1;
+
+ void showIndividualHistoryPixelInfo(IndividualHistoryPixelInfo pixelInfo) {
+ mode.value = 1;
+ changeVar.value = changeVar.value ? false : true;
+ draggableController.animateTo(
+ 0.6,
+ duration: Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ );
+ currentHeader = PixelInfoHeader(
+ address: pixelInfo.address,
+ visitCount: pixelInfo.visitCount!,
+ mode: PixelMode.individualHistory,
+ );
+ currentBody = IndividualHistoryList(
+ visitList: pixelInfo.visitList ?? [],
+ );
+ mode.value = 1;
+ }
+
+ void showIndividualModePixelInfo(IndividualModePixelInfo pixelInfo) {
+ mode.value = 2;
+ changeVar.value = changeVar.value ? false : true;
+ draggableController.animateTo(
+ 0.6,
+ duration: Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ );
+ currentHeader = PixelInfoHeader(
+ address: pixelInfo.address,
+ visitCount: pixelInfo.visitCount!,
+ mode: PixelMode.individualMode,
+ );
+ currentBody = VisitedUserList(
+ pixelOwnerUser: pixelInfo.pixelOwnerUser!,
+ visitList: pixelInfo.visitList ?? [],
+ );
+ mode.value = 2;
+ }
+
+ minimize() {
+ currentHeader = StepStats();
+ currentBody = StepStatsBody();
+ mode.value = 0;
+ draggableController.animateTo(
+ 0.1,
+ duration: Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ );
+ }
+
+ void changeStepStatIfMinimized() {
+ if (draggableController.size <= 0.111 && size > draggableController.size) {
+ currentHeader = StepStats();
+ currentBody = StepStatsBody();
+ mode.value = 0;
+ }
+ size = draggableController.size;
+ }
+
+ getBody() {
+ if (mode.value == 0 && changeVar.value) {
+ // changeVar.value = changeVar.value ? false : true;
+ }
+ return currentBody;
+ }
+
+ getHeader() {
+ if (mode.value == 0 && changeVar.value) {
+ // changeVar.value = changeVar.value ? false : true;
+ }
+ return currentHeader;
+ }
+}
diff --git a/lib/controllers/map_controller.dart b/lib/controllers/map_controller.dart
index c4ad0c36..6e883b02 100644
--- a/lib/controllers/map_controller.dart
+++ b/lib/controllers/map_controller.dart
@@ -9,12 +9,19 @@ import 'package:location/location.dart';
import '../enums/pixel_mode.dart';
import '../models/individual_history_pixel.dart';
import '../models/individual_mode_pixel.dart';
+import '../models/user_pixel_count.dart';
+import '../service/location_service.dart';
import '../service/pixel_service.dart';
+import '../service/user_service.dart';
import '../utils/user_manager.dart';
import '../widgets/pixel.dart';
+import 'bottom_sheet_controller.dart';
class MapController extends GetxController {
final PixelService pixelService = PixelService();
+ final UserService userService = UserService();
+ final BottomSheetController bottomSheetController =
+ Get.find();
static const String darkMapStylePath =
'assets/map_style/dark_map_style_with_landmarks.txt';
@@ -35,6 +42,9 @@ class MapController extends GetxController {
RxList pixels = [].obs;
RxList markers = [].obs;
RxBool isLoading = true.obs;
+ final RxInt selectedType = 0.obs;
+ final RxInt currentPixelCount = 0.obs;
+ final RxInt accumulatePixelCount = 0.obs;
Timer? _cameraIdleTimer;
@@ -44,6 +54,7 @@ class MapController extends GetxController {
await _loadMapStyle();
await initCurrentLocation();
_updateLatestPixel();
+ await updateCurrentPixel();
await occupyPixel();
updatePixels();
_createUserMarker();
@@ -51,6 +62,20 @@ class MapController extends GetxController {
_trackPixels();
}
+ onHidden() {
+ bottomSheetController.minimize();
+ }
+
+ updateCurrentPixel() async {
+ UserPixelCount pixelCount = await userService.getUserPixelCount();
+ currentPixelCount.value = pixelCount.currentPixelCount!;
+ accumulatePixelCount.value = pixelCount.accumulatePixelCount!;
+ }
+
+ getSelectedType() {
+ return selectedType.value;
+ }
+
void onCameraIdle() {
_cameraIdleTimer = Timer(Duration(milliseconds: 300), updatePixels);
}
@@ -60,6 +85,16 @@ class MapController extends GetxController {
_cameraIdleTimer?.cancel();
}
+ focusOnCurrentLocation() {
+ currentCameraPosition = CameraPosition(
+ target: LatLng(currentLocation.latitude!, currentLocation.longitude!),
+ zoom: 16.0,
+ );
+ googleMapController?.animateCamera(
+ CameraUpdate.newCameraPosition(currentCameraPosition),
+ );
+ }
+
void _trackUserLocation() {
location.onLocationChanged.listen((newLocation) async {
currentLocation = newLocation;
@@ -73,7 +108,7 @@ class MapController extends GetxController {
Future initCurrentLocation() async {
try {
- currentLocation = await location.getLocation();
+ currentLocation = LocationService().currentLocation!;
currentCameraPosition = CameraPosition(
target: LatLng(currentLocation.latitude!, currentLocation.longitude!),
zoom: 16.0,
@@ -182,6 +217,7 @@ class MapController extends GetxController {
currentLongitude: currentLocation.longitude!,
);
updatePixels();
+ await updateCurrentPixel();
}
isPixelChanged() {
@@ -194,8 +230,10 @@ class MapController extends GetxController {
latestPixel['y'] != currentPixel['y'];
}
- void changePixelMode(String pixelModeKrName) {
- currentPixelMode.value = PixelMode.fromKrName(pixelModeKrName);
+ void changePixelMode(int type) {
+ selectedType.value = type;
+ currentPixelMode.value = PixelMode.fromInt(type);
+ bottomSheetController.minimize();
updatePixels();
}
@@ -234,4 +272,12 @@ class MapController extends GetxController {
double _toRadians(double degree) {
return degree * math.pi / 180;
}
+
+ getPixelCount() {
+ if (currentPixelMode.value == PixelMode.individualHistory) {
+ return accumulatePixelCount.value;
+ } else {
+ return currentPixelCount.value;
+ }
+ }
}
diff --git a/lib/controllers/my_page_controller.dart b/lib/controllers/my_page_controller.dart
index 4e9f07d3..18466c76 100644
--- a/lib/controllers/my_page_controller.dart
+++ b/lib/controllers/my_page_controller.dart
@@ -1,5 +1,4 @@
import 'package:get/get.dart';
-import 'package:location/location.dart';
import '../models/user.dart';
import '../models/user_pixel_count.dart';
@@ -17,13 +16,6 @@ class MyPageController extends GetxController {
User userInfo = await userService.getCurrentUserInfo();
currentUserInfo.value = userInfo;
await _updatePixelCount();
- _trackPixelCount();
- }
-
- _trackPixelCount() {
- Location().onLocationChanged.listen((newLocation) async {
- await _updatePixelCount();
- });
}
_updatePixelCount() async {
diff --git a/lib/controllers/navigation_controller.dart b/lib/controllers/navigation_controller.dart
index 3f2eca2c..b556c3e4 100644
--- a/lib/controllers/navigation_controller.dart
+++ b/lib/controllers/navigation_controller.dart
@@ -5,10 +5,12 @@ import '../screens/map_screen.dart';
import '../screens/my_page_screen.dart';
import '../screens/ranking_screen.dart';
import '../widgets/common/app_bar.dart';
+import 'map_controller.dart';
import 'ranking_controller.dart';
class NavigationController extends GetxController {
final RankingController rankingController = Get.find();
+ final MapController mapController = Get.find();
final RxInt selectedIndex = 0.obs;
static List tabPages = [
const MapScreen(),
@@ -24,10 +26,13 @@ class NavigationController extends GetxController {
];
void changeIndex(int index) {
- selectedIndex(index);
- if (index == 1) {
+ if (selectedIndex.value != 1 && index == 1) {
rankingController.onVisible();
}
+ if (selectedIndex.value == 0 && index != 0) {
+ mapController.onHidden();
+ }
+ selectedIndex(index);
}
Widget getCurrentPage() {
diff --git a/lib/enums/pixel_mode.dart b/lib/enums/pixel_mode.dart
index bfef0193..a543f287 100644
--- a/lib/enums/pixel_mode.dart
+++ b/lib/enums/pixel_mode.dart
@@ -1,15 +1,26 @@
enum PixelMode {
- individualMode(koreanName : '개인전'),
- individualHistory(koreanName : '개인 기록'),
- groupMode(koreanName : '그룹전');
+ individualMode(koreanName: '개인전'),
+ individualHistory(koreanName: '개인 기록'),
+ groupMode(koreanName: '그룹전');
const PixelMode({required this.koreanName});
final String koreanName;
static PixelMode fromKrName(String krName) {
- return PixelMode.values.firstWhere((mode) => mode.koreanName == krName,
- orElse: () => throw ArgumentError('No enum value with krName $krName'),
+ return PixelMode.values.firstWhere(
+ (mode) => mode.koreanName == krName,
+ orElse: () => throw ArgumentError('No enum value with krName $krName'),
);
}
+
+ static PixelMode fromInt(int number) {
+ if (number == 0) {
+ return PixelMode.individualHistory;
+ } else if (number == 1) {
+ return PixelMode.individualMode;
+ } else {
+ return PixelMode.groupMode;
+ }
+ }
}
diff --git a/lib/main.dart b/lib/main.dart
index 22c37954..4f957b29 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -11,16 +11,15 @@ import 'screens/policy_screen.dart';
import 'screens/setting_screen.dart';
import 'screens/sign_up_screen.dart';
import 'service/auth_service.dart';
+import 'service/location_service.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
await GetStorage.init();
KakaoSdk.init(nativeAppKey: dotenv.env['NATIVE_APP_KEY']!);
-
+ await LocationService().initCurrentLocation();
String initialRoute = await AuthService().isLogin() ? '/main' : '/permission';
-
- await Future.delayed(Duration(seconds: 2));
runApp(
MyApp(
initialRoute: initialRoute,
@@ -35,7 +34,6 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
-
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
child: GetMaterialApp(
@@ -51,7 +49,10 @@ class MyApp extends StatelessWidget {
GetPage(name: '/setting', page: () => const SettingScreen()),
GetPage(name: '/signup', page: () => const SignUpScreen()),
GetPage(name: '/policy', page: () => const PolicyScreen()),
- GetPage(name: '/permission', page: () => const PermissionRequestScreen()),
+ GetPage(
+ name: '/permission',
+ page: () => const PermissionRequestScreen(),
+ ),
],
),
);
diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart
index 6f108118..89e6060c 100644
--- a/lib/screens/main_screen.dart
+++ b/lib/screens/main_screen.dart
@@ -2,31 +2,46 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../constants/app_colors.dart';
+import '../controllers/bottom_sheet_controller.dart';
+import '../controllers/map_controller.dart';
import '../controllers/my_page_controller.dart';
import '../controllers/navigation_controller.dart';
import '../controllers/ranking_controller.dart';
import '../widgets/common/naviagtion_bar.dart';
-class MainScreen extends StatelessWidget {
+class MainScreen extends StatefulWidget {
const MainScreen({super.key});
+ @override
+ State createState() => _MainScreenState();
+}
+
+class _MainScreenState extends State {
@override
Widget build(BuildContext context) {
Get.put(MyPageController());
Get.put(RankingController());
+ Get.put(BottomSheetController());
+ Get.put(MapController());
final NavigationController navigationController =
Get.put(NavigationController());
return Scaffold(
- appBar: PreferredSize(
- preferredSize: const Size.fromHeight(kToolbarHeight),
- child: Obx(() => navigationController.getCurrentAppBar()),
- ),
+ appBar: navigationController.selectedIndex.value == 0
+ ? null
+ : PreferredSize(
+ preferredSize: const Size.fromHeight(kToolbarHeight),
+ child: Obx(() => navigationController.getCurrentAppBar()),
+ ),
body: Obx(
- () => SafeArea(child: navigationController.getCurrentPage()),
+ () => navigationController.getCurrentPage(),
),
backgroundColor: AppColors.background,
- bottomNavigationBar: const CustomBottomNavigationBar(),
+ bottomNavigationBar: CustomBottomNavigationBar((index) {
+ setState(() {
+ navigationController.changeIndex(index);
+ });
+ }),
);
}
}
diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart
index b6e0a4c6..77c62b4b 100644
--- a/lib/screens/map_screen.dart
+++ b/lib/screens/map_screen.dart
@@ -5,15 +5,17 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../controllers/map_controller.dart';
import '../controllers/pixel_info_controller.dart';
import '../controllers/walking_controller.dart';
-import '../widgets/map/mode_change_button.dart';
-import '../widgets/map/step_stats.dart';
+import '../widgets/map/bottom_sheet/map_bottom_sheet.dart';
+import '../widgets/map/current_location_button.dart';
+import '../widgets/map/mode_change_toggle.dart';
+import '../widgets/map/pixel_count_info.dart';
class MapScreen extends StatelessWidget {
const MapScreen({super.key});
@override
Widget build(BuildContext context) {
- final MapController mapController = Get.put(MapController());
+ final MapController mapController = Get.find();
Get.put(PixelInfoController());
Get.put(WalkingController());
@@ -29,8 +31,6 @@ class MapScreen extends StatelessWidget {
Obx(() {
return GoogleMap(
mapType: MapType.normal,
- myLocationButtonEnabled: true,
- myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(
mapController.currentLocation.latitude!,
@@ -50,16 +50,23 @@ class MapScreen extends StatelessWidget {
}),
Column(
children: [
- const Padding(
- padding: EdgeInsets.only(top: 65.0),
- ),
- ModeChangeButton(),
- const Padding(
- padding: EdgeInsets.only(bottom: 32),
- child: StepStats(),
+ SizedBox(height: 60),
+ ModeChangeToggle(),
+ Spacer(),
+ Container(
+ padding: EdgeInsets.symmetric(vertical: 0, horizontal: 10),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ PixelCountInfo(count: 128),
+ CurrentLocationButton(),
+ ],
+ ),
),
+ SizedBox(height: MediaQuery.of(context).size.height * 0.11),
],
),
+ MapBottomSheet(),
],
);
}
diff --git a/lib/service/location_service.dart b/lib/service/location_service.dart
new file mode 100644
index 00000000..661e4380
--- /dev/null
+++ b/lib/service/location_service.dart
@@ -0,0 +1,17 @@
+import 'package:location/location.dart';
+
+class LocationService {
+ static final LocationService _instance = LocationService._internal();
+ final Location location = Location();
+ LocationData? currentLocation;
+
+ LocationService._internal();
+
+ factory LocationService() {
+ return _instance;
+ }
+
+ initCurrentLocation() async {
+ currentLocation = await location.getLocation();
+ }
+}
diff --git a/lib/widgets/common/app_bar.dart b/lib/widgets/common/app_bar.dart
index f716f35e..17a3e85d 100644
--- a/lib/widgets/common/app_bar.dart
+++ b/lib/widgets/common/app_bar.dart
@@ -9,10 +9,7 @@ class MapAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return AppBar(
- backgroundColor: AppColors.background,
- title: AppBarTitle(title: "지도"),
- );
+ return Container();
}
}
diff --git a/lib/widgets/common/naviagtion_bar.dart b/lib/widgets/common/naviagtion_bar.dart
index b3f00f8a..249446eb 100644
--- a/lib/widgets/common/naviagtion_bar.dart
+++ b/lib/widgets/common/naviagtion_bar.dart
@@ -5,27 +5,36 @@ import '../../constants/app_colors.dart';
import '../../controllers/navigation_controller.dart';
class CustomBottomNavigationBar extends GetView {
- const CustomBottomNavigationBar({super.key});
+ const CustomBottomNavigationBar(this.onTap, {super.key});
+
+ final Function(int) onTap;
@override
Widget build(BuildContext context) {
return Obx(
- () => BottomNavigationBar(
- type: BottomNavigationBarType.fixed,
- currentIndex: controller.selectedIndex.value,
- onTap: controller.changeIndex,
- items: const [
- BottomNavigationBarItem(icon: Icon(Icons.map), label: "지도"),
- BottomNavigationBarItem(icon: Icon(Icons.leaderboard), label: "랭킹"),
- // BottomNavigationBarItem(icon: Icon(Icons.group), label: "그룹"),
- BottomNavigationBarItem(
- icon: Icon(Icons.account_circle),
- label: "마이",
+ () => Container(
+ decoration: BoxDecoration(
+ border: Border(
+ top: BorderSide(color: AppColors.backgroundThird, width: 0.5),
),
- ],
- selectedItemColor: AppColors.primary,
- unselectedItemColor: AppColors.textSecondary,
- backgroundColor: AppColors.navigationBarColor,
+ ),
+ child: BottomNavigationBar(
+ type: BottomNavigationBarType.fixed,
+ currentIndex: controller.selectedIndex.value,
+ onTap: onTap,
+ items: const [
+ BottomNavigationBarItem(icon: Icon(Icons.map), label: "지도"),
+ BottomNavigationBarItem(icon: Icon(Icons.leaderboard), label: "랭킹"),
+ // BottomNavigationBarItem(icon: Icon(Icons.group), label: "그룹"),
+ BottomNavigationBarItem(
+ icon: Icon(Icons.account_circle),
+ label: "마이",
+ ),
+ ],
+ selectedItemColor: AppColors.primary,
+ unselectedItemColor: AppColors.textSecondary,
+ backgroundColor: AppColors.navigationBarColor,
+ ),
),
);
}
diff --git a/lib/widgets/map/bottom_sheet/individual_history_list.dart b/lib/widgets/map/bottom_sheet/individual_history_list.dart
new file mode 100644
index 00000000..2b3775ea
--- /dev/null
+++ b/lib/widgets/map/bottom_sheet/individual_history_list.dart
@@ -0,0 +1,59 @@
+import 'package:flutter/material.dart';
+
+import '../../../constants/app_colors.dart';
+import '../../../constants/text_styles.dart';
+
+class IndividualHistoryList extends StatelessWidget {
+ const IndividualHistoryList({super.key, required this.visitList});
+
+ final List visitList;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverList.list(
+ children: [
+ SizedBox(height: 10),
+ for (int i = 0; i < visitList.length; i++)
+ IndividualHistoryListElement(
+ historyDate: visitList[i],
+ ),
+ ],
+ );
+ }
+}
+
+class IndividualHistoryListElement extends StatelessWidget {
+ IndividualHistoryListElement({super.key, required this.historyDate});
+
+ final DateTime historyDate;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.fromLTRB(20, 5, 20, 5),
+ child: Container(
+ padding: EdgeInsets.symmetric(vertical: 16, horizontal: 20),
+ decoration: BoxDecoration(
+ color: AppColors.backgroundThird,
+ borderRadius: BorderRadius.all(Radius.circular(12)),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.access_time_filled,
+ size: 16,
+ color: AppColors.textSecondary,
+ ),
+ SizedBox(
+ width: 20,
+ ),
+ Text(
+ '${historyDate.year}년 ${historyDate.month}월 ${historyDate.day}일',
+ style: TextStyles.fs17w400cTextPrimary,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/map/bottom_sheet/map_bottom_sheet.dart b/lib/widgets/map/bottom_sheet/map_bottom_sheet.dart
new file mode 100644
index 00000000..bdfe9e42
--- /dev/null
+++ b/lib/widgets/map/bottom_sheet/map_bottom_sheet.dart
@@ -0,0 +1,70 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../../constants/app_colors.dart';
+import '../../../controllers/bottom_sheet_controller.dart';
+
+class MapBottomSheet extends StatelessWidget {
+ const MapBottomSheet({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ var bottomSheetController = Get.find();
+
+ return NotificationListener(
+ onNotification: (DraggableScrollableNotification notification) {
+ bottomSheetController.changeStepStatIfMinimized();
+ return true;
+ },
+ child: DraggableScrollableSheet(
+ controller: bottomSheetController.draggableController,
+ snap: true,
+ initialChildSize: 0.11,
+ minChildSize: 0.11,
+ maxChildSize: 0.6,
+ builder: (BuildContext context, scrollController) {
+ return Container(
+ clipBehavior: Clip.hardEdge,
+ decoration: BoxDecoration(
+ color: AppColors.backgroundSecondary,
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(32),
+ topRight: Radius.circular(32),
+ ),
+ ),
+ child: CustomScrollView(
+ controller: scrollController,
+ slivers: [
+ SliverToBoxAdapter(
+ child: Center(
+ child: Container(
+ decoration: BoxDecoration(
+ color: AppColors.backgroundThird,
+ borderRadius:
+ const BorderRadius.all(Radius.circular(10)),
+ ),
+ height: 4,
+ width: 40,
+ margin: const EdgeInsets.fromLTRB(0, 10, 0, 0),
+ ),
+ ),
+ ),
+ SliverToBoxAdapter(
+ child: Obx(
+ () => Container(
+ padding: EdgeInsets.symmetric(horizontal: 20.0),
+ child: bottomSheetController.getHeader(),
+ ),
+ ),
+ ),
+ Obx(() {
+ return bottomSheetController.getBody();
+ }),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/map/bottom_sheet/pixel_info_header.dart b/lib/widgets/map/bottom_sheet/pixel_info_header.dart
new file mode 100644
index 00000000..88371df7
--- /dev/null
+++ b/lib/widgets/map/bottom_sheet/pixel_info_header.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/cupertino.dart';
+
+import '../../../constants/text_styles.dart';
+import '../../../enums/pixel_mode.dart';
+
+class PixelInfoHeader extends StatelessWidget {
+ const PixelInfoHeader({
+ super.key,
+ required this.address,
+ required this.visitCount,
+ required this.mode,
+ });
+
+ final String? address;
+ final int visitCount;
+ final PixelMode mode;
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ children: [
+ Text(
+ address ?? "대한민국",
+ style: TextStyles.fs24w900cTextPrimary,
+ ),
+ Spacer(),
+ Text(
+ createSubPixelInfo(),
+ style: TextStyles.fs17w400cTextSecondary,
+ ),
+ ],
+ );
+ }
+
+ createSubPixelInfo() {
+ if (mode == PixelMode.individualHistory) {
+ return "$visitCount번째 방문";
+ } else if (mode == PixelMode.individualMode) {
+ return "오늘 $visitCount명이 점령";
+ }
+ }
+}
diff --git a/lib/widgets/map/bottom_sheet/step_stats.dart b/lib/widgets/map/bottom_sheet/step_stats.dart
new file mode 100644
index 00000000..d9105feb
--- /dev/null
+++ b/lib/widgets/map/bottom_sheet/step_stats.dart
@@ -0,0 +1,50 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:intl/intl.dart';
+
+import '../../../constants/text_styles.dart';
+import '../../../controllers/walking_controller.dart';
+
+class StepStats extends StatelessWidget {
+ const StepStats({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ var walkingController = Get.find();
+ return Obx(
+ () => Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Text(
+ NumberFormat('###,###,###')
+ .format(walkingController.currentStep.value),
+ style: TextStyles.fs24w900cTextPrimary,
+ ),
+ Text(
+ ' 걸음',
+ style: TextStyles.fs17w400cTextSecondary,
+ ),
+ Spacer(),
+ Text(
+ '${walkingController.getCurrentCalorie()} kcal · ${walkingController.getCurrentTravelDistance()}km',
+ style: TextStyles.fs17w400cTextSecondary,
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+// Todo: 추후에 대시보드 등의 형식으로 변경 필요
+class StepStatsBody extends StatelessWidget {
+ const StepStatsBody({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverToBoxAdapter(
+ child: SizedBox(
+ width: 10,
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/map/bottom_sheet/visited_user_list.dart b/lib/widgets/map/bottom_sheet/visited_user_list.dart
new file mode 100644
index 00000000..f22140bd
--- /dev/null
+++ b/lib/widgets/map/bottom_sheet/visited_user_list.dart
@@ -0,0 +1,190 @@
+import 'package:flutter/cupertino.dart';
+import 'package:intl/intl.dart';
+
+import '../../../constants/app_colors.dart';
+import '../../../constants/text_styles.dart';
+import '../../../models/individual_mode_pixel_info.dart';
+
+class VisitedUserList extends StatelessWidget {
+ final PixelOwnerUser pixelOwnerUser;
+ final List visitList;
+
+ const VisitedUserList({
+ super.key,
+ required this.visitList,
+ required this.pixelOwnerUser,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverList.list(
+ children: [
+ SizedBox(height: 10),
+ OwnerInfo(
+ pixelOwnerUser: pixelOwnerUser,
+ ),
+ if (visitList.isEmpty) NoVisitedUserMessage(),
+ for (int i = 0; i < visitList.length; i++)
+ VisitedUserListElement(
+ visitedUser: visitList[i],
+ ),
+ ],
+ );
+ }
+}
+
+class OwnerInfo extends StatelessWidget {
+ const OwnerInfo({
+ super.key,
+ required this.pixelOwnerUser,
+ });
+
+ final PixelOwnerUser pixelOwnerUser;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20.0),
+ child: Container(
+ decoration: BoxDecoration(
+ color: AppColors.backgroundThird,
+ borderRadius: BorderRadius.all(Radius.circular(16)),
+ ),
+ padding: EdgeInsets.all(20),
+ child: Row(
+ children: [
+ ClipOval(
+ child: pixelOwnerUser.profileImageUrl != null
+ ? Image.network(
+ pixelOwnerUser.profileImageUrl!,
+ cacheWidth: 100,
+ width: 44,
+ height: 44,
+ fit: BoxFit.cover,
+ )
+ : Image.asset(
+ 'assets/images/default_profile_image.png',
+ width: 44,
+ height: 44,
+ ),
+ ),
+ SizedBox(width: 10),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ pixelOwnerUser.nickname ?? "알 수 없음",
+ style: TextStyles.fs17w600cTextPrimary,
+ ),
+ Row(
+ children: [
+ Text(
+ '${NumberFormat('###,###,###').format(pixelOwnerUser.currentPixelCount)}px',
+ style: TextStyles.fs14w500cPrimary,
+ ),
+ Text(
+ ' · 누적 ${NumberFormat('###,###,###').format(pixelOwnerUser.accumulatePixelCount)}px',
+ style: TextStyles.fs14w500cTextSecondary,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Image.asset(
+ 'assets/images/current_pixel_icon.png',
+ width: 44,
+ height: 44,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class VisitedUserListElement extends StatelessWidget {
+ const VisitedUserListElement({
+ super.key,
+ required this.visitedUser,
+ });
+
+ final VisitList visitedUser;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 40.0),
+ child: Container(
+ padding: EdgeInsets.symmetric(vertical: 20),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: AppColors.backgroundThird,
+ width: 0.5,
+ ),
+ ),
+ ),
+ child: Row(
+ children: [
+ ClipOval(
+ child: visitedUser.profileImageUrl != null
+ ? Image.network(
+ visitedUser.profileImageUrl!,
+ cacheWidth: 100,
+ width: 44,
+ height: 44,
+ fit: BoxFit.cover,
+ )
+ : Image.asset(
+ 'assets/images/default_profile_image.png',
+ width: 44,
+ height: 44,
+ ),
+ ),
+ SizedBox(width: 10),
+ Expanded(
+ child: Text(
+ visitedUser.nickname ?? "알 수 없음",
+ style: TextStyles.fs17w600cTextPrimary,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class NoVisitedUserMessage extends StatelessWidget {
+ const NoVisitedUserMessage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(20.0),
+ child: Container(
+ padding: const EdgeInsets.all(20.0),
+ decoration: BoxDecoration(
+ color: AppColors.backgroundThird,
+ borderRadius: BorderRadius.all(Radius.circular(16)),
+ ),
+ child: Center(
+ child: Column(
+ children: [
+ Text(
+ "오늘 방문한 사람이 없습니다.",
+ style: TextStyles.fs20w700cTextPrimary,
+ ),
+ Text(
+ "걸어서 픽셀을 차지 해보세요!",
+ style: TextStyles.fs17w700cPrimary,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/map/current_location_button.dart b/lib/widgets/map/current_location_button.dart
new file mode 100644
index 00000000..40044a82
--- /dev/null
+++ b/lib/widgets/map/current_location_button.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../constants/app_colors.dart';
+import '../../controllers/map_controller.dart';
+
+class CurrentLocationButton extends StatefulWidget {
+ const CurrentLocationButton({super.key});
+
+ @override
+ createState() => _CurrentLocationButtonState();
+}
+
+class _CurrentLocationButtonState extends State {
+ final MapController mapController = Get.find();
+ Color _buttonColor = AppColors.backgroundSecondary; // 기본 배경 색상
+ final Color _pressedColor = Colors.grey; // 눌렀을 때의 배경 색상
+
+ void _onPointerDown(DragDownDetails event) {
+ setState(() {
+ _buttonColor = _pressedColor;
+ });
+ }
+
+ void _onPointerUp() {
+ setState(() {
+ _buttonColor = AppColors.backgroundSecondary;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ mapController.focusOnCurrentLocation();
+ },
+ onPanDown: _onPointerDown,
+ onPanEnd: (details) => _onPointerUp(),
+ onPanCancel: () => _onPointerUp(),
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.all(Radius.circular(12)),
+ color: _buttonColor,
+ ),
+ padding: EdgeInsets.all(10),
+ child: Icon(
+ Icons.my_location,
+ color: Colors.white,
+ size: 20, // 아이콘 크기
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/map/individual_history_pixel_info_bottom_sheet.dart b/lib/widgets/map/individual_history_pixel_info_bottom_sheet.dart
deleted file mode 100644
index 9a800e6c..00000000
--- a/lib/widgets/map/individual_history_pixel_info_bottom_sheet.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../models/individual_history_pixel_info.dart';
-import 'individual_visit_history_list_view.dart';
-
-class IndividualHistoryPixelInfoBottomSheet extends StatelessWidget {
- const IndividualHistoryPixelInfoBottomSheet(
- {super.key, required this.pixelInfo,});
-
- final IndividualHistoryPixelInfo pixelInfo;
-
- @override
- Widget build(BuildContext context) {
- return Container(
- decoration: const BoxDecoration(
- color: Color(0xFF374957),
- borderRadius: BorderRadius.only(
- topRight: Radius.circular(20),
- topLeft: Radius.circular(20),
- ),
- ),
- child: SizedBox(
- height: 400,
- width: 380,
- child: Padding(
- padding: const EdgeInsets.fromLTRB(30, 20, 30, 0),
- child: Column(
- children: [
- Align(
- alignment: Alignment.topLeft,
- child: Text(
- '${pixelInfo.address ?? '대한민국'} ${pixelInfo.addressNumber ?? 'n'}번째 픽셀',
- style: TextStyle(
- fontSize: 22,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- ),
- Align(
- alignment: Alignment.topLeft,
- child: Text(
- '${pixelInfo.visitCount}번 방문했어요!',
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,),
- ),
- ),
- SizedBox(
- height: 10,
- ),
- IndividualVisitHistoryListView(visitList: pixelInfo.visitList!),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/map/individual_mode_pixel_info_bottom_sheet.dart b/lib/widgets/map/individual_mode_pixel_info_bottom_sheet.dart
deleted file mode 100644
index 44c1ee9f..00000000
--- a/lib/widgets/map/individual_mode_pixel_info_bottom_sheet.dart
+++ /dev/null
@@ -1,66 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../models/individual_mode_pixel_info.dart';
-import 'pixel_owner_info.dart';
-import 'visited_user_list_view.dart';
-
-class IndividualModePixelInfoBottomSheet extends StatelessWidget {
- IndividualModePixelInfoBottomSheet({super.key, required this.pixelInfo,});
-
- final IndividualModePixelInfo pixelInfo;
-
- @override
- Widget build(BuildContext context) {
- return Container(
- decoration: const BoxDecoration(
- color: Color(0xFF374957),
- borderRadius: BorderRadius.only(
- topRight: Radius.circular(20),
- topLeft: Radius.circular(20),
- ),
- ),
- child: SizedBox(
- height: 400,
- width: 380,
- child: Padding(
- padding: const EdgeInsets.fromLTRB(30, 20, 30, 0),
- child: Column(
- children: [
- Align(
- alignment: Alignment.topLeft,
- child: Text(
- '${pixelInfo.address ?? '대한민국'} ${pixelInfo.addressNumber ?? 'n'}번째 픽셀',
- style: TextStyle(
- fontSize: 22,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- ),
- SizedBox(
- height: 10,
- ),
- PixelOwnerInfo(pixelOwnerUser: pixelInfo.pixelOwnerUser!,),
- const SizedBox(height: 10),
- Align(
- alignment: Alignment.topLeft,
- child: Text(
- '오늘 ${pixelInfo.visitCount}명이 차지했었어요!',
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- ),
- SizedBox(
- height: 10,
- ),
- VisitedUserListView(visitList: pixelInfo.visitList!,),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/map/individual_visit_history_list_view.dart b/lib/widgets/map/individual_visit_history_list_view.dart
deleted file mode 100644
index ca818a4f..00000000
--- a/lib/widgets/map/individual_visit_history_list_view.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:flutter/material.dart';
-
-class IndividualVisitHistoryListView extends StatelessWidget {
- const IndividualVisitHistoryListView({super.key, required this.visitList});
-
- final List visitList;
-
- @override
- Widget build(BuildContext context) {
- return Expanded(
- child: Container(
- decoration: const BoxDecoration(
- color: Color(0xFF8B8B8B),
- borderRadius: BorderRadius.only(
- topRight: Radius.circular(20),
- topLeft: Radius.circular(20),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
- child: ListView.builder(
- itemCount: visitList.length,
- itemBuilder: (context, int index) {
- return Padding(
- padding: const EdgeInsets.all(5.0),
- child: Text(
- "${visitList[index].year}년 ${visitList[index].month}월 ${visitList[index].day}일",
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- );
- },
- shrinkWrap: true,
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/map/mode_change_button.dart b/lib/widgets/map/mode_change_button.dart
deleted file mode 100644
index 75bbdd80..00000000
--- a/lib/widgets/map/mode_change_button.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-import 'package:dropdown_button2/dropdown_button2.dart';
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-
-import '../../controllers/map_controller.dart';
-import '../../enums/pixel_mode.dart';
-
-class ModeChangeButton extends StatelessWidget {
- const ModeChangeButton({super.key});
-
- @override
- Widget build(BuildContext context) {
- var mapController = Get.find();
-
- return Expanded(
- child: Align(
- alignment: Alignment.topRight,
- child: DropdownButtonHideUnderline(
- child: Obx(() {
- return DropdownButton2(
- alignment: Alignment.center,
- isExpanded: true,
- items: PixelMode.values
- .map(
- (PixelMode pixelMode) => DropdownMenuItem(
- value: pixelMode.koreanName,
- child: Text(
- pixelMode.koreanName,
- style: const TextStyle(
- fontSize: 14,
- color: Colors.white,
- ),
- ),
- ),
- )
- .toList(),
- onChanged: (pixelModeKrName) {
- mapController.changePixelMode(pixelModeKrName!);
- },
- value: mapController.currentPixelMode.value.koreanName,
- buttonStyleData: ButtonStyleData(
- padding: EdgeInsets.symmetric(horizontal: 16),
- height: 40,
- width: 120,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(14),
- color: Colors.blueAccent,
- ),
- ),
- dropdownStyleData: DropdownStyleData(
- maxHeight: 200,
- width: 120,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(14),
- color: Colors.blueAccent,
- ),
- scrollbarTheme: ScrollbarThemeData(
- radius: const Radius.circular(40),
- ),
- ),
- menuItemStyleData: const MenuItemStyleData(
- height: 40,
- ),
- );
- }),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/map/mode_change_toggle.dart b/lib/widgets/map/mode_change_toggle.dart
new file mode 100644
index 00000000..c60efb63
--- /dev/null
+++ b/lib/widgets/map/mode_change_toggle.dart
@@ -0,0 +1,58 @@
+import 'package:animated_toggle_switch/animated_toggle_switch.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../constants/app_colors.dart';
+import '../../controllers/map_controller.dart';
+
+class ModeChangeToggle extends StatelessWidget {
+ ModeChangeToggle({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final MapController mapController = Get.find();
+ return Obx(() {
+ return Center(
+ child: AnimatedToggleSwitch.size(
+ current: mapController.getSelectedType(),
+ style: ToggleStyle(
+ backgroundColor: Color(0xF21D1D1D),
+ indicatorColor: AppColors.buttonColor,
+ borderColor: Colors.transparent,
+ borderRadius: BorderRadius.circular(24.0),
+ indicatorBorderRadius: BorderRadius.circular(24.0),
+ ),
+ // values: const [0, 1, 2],
+ values: const [0, 1],
+ iconOpacity: 1.0,
+ selectedIconScale: 1.0,
+ indicatorSize: Size.fromWidth(90),
+ height: 40,
+ iconAnimationType: AnimationType.onHover,
+ styleAnimationType: AnimationType.onHover,
+ spacing: 2.0,
+ customIconBuilder: (context, local, global) {
+ // final text = const ['개인 기록', '개인전', '그룹전'][local.index];
+ final text = const ['개인 기록', '개인전'][local.index];
+ return Center(
+ child: Text(
+ text,
+ style: TextStyle(
+ color: Color.lerp(
+ Colors.white,
+ Colors.black,
+ local.animationValue,
+ ),
+ fontSize: 14,
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ );
+ },
+ borderWidth: 0.0,
+ onChanged: (i) => mapController.changePixelMode(i),
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/widgets/map/pixel_count_info.dart b/lib/widgets/map/pixel_count_info.dart
new file mode 100644
index 00000000..85dcdbcc
--- /dev/null
+++ b/lib/widgets/map/pixel_count_info.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/cupertino.dart';
+import 'package:get/get.dart';
+
+import '../../constants/app_colors.dart';
+import '../../constants/text_styles.dart';
+import '../../controllers/map_controller.dart';
+import '../../enums/pixel_mode.dart';
+
+class PixelCountInfo extends StatelessWidget {
+ const PixelCountInfo({
+ super.key,
+ required this.count,
+ });
+
+ final int count;
+
+ @override
+ Widget build(BuildContext context) {
+ final MapController mapController = Get.find();
+ return Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.all(Radius.circular(12)),
+ color: AppColors.backgroundSecondary,
+ ),
+ padding: EdgeInsets.symmetric(vertical: 10, horizontal: 8),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Obx(() {
+ return Image.asset(
+ mapController.currentPixelMode.value ==
+ PixelMode.individualHistory
+ ? "assets/images/accumulate_pixel_icon.png"
+ : "assets/images/current_pixel_icon.png",
+ width: 28,
+ );
+ }),
+ SizedBox(
+ width: 10,
+ ),
+ Obx(() {
+ return Text(
+ "${mapController.getPixelCount()}",
+ style: TextStyles.fs17w600cTextPrimary,
+ );
+ }),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/map/pixel_owner_info.dart b/lib/widgets/map/pixel_owner_info.dart
deleted file mode 100644
index e55c3b50..00000000
--- a/lib/widgets/map/pixel_owner_info.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../models/individual_mode_pixel_info.dart';
-
-class PixelOwnerInfo extends StatelessWidget {
- const PixelOwnerInfo({super.key, required this.pixelOwnerUser});
-
- final PixelOwnerUser pixelOwnerUser;
-
- @override
- Widget build(BuildContext context) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- ClipOval(
- child: pixelOwnerUser.profileImageUrl != null
- ? Image.network(
- pixelOwnerUser.profileImageUrl!,
- width: 50,
- height: 50,
- fit: BoxFit.cover,
- )
- : Image.asset(
- 'assets/images/default_profile_image.png',
- width: 50,
- height: 50,
- ),
- ),
- Text(
- pixelOwnerUser.nickname!,
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- Text(
- pixelOwnerUser.currentPixelCount.toString(),
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- Text(
- pixelOwnerUser.accumulatePixelCount.toString(),
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- ],
- );
- }
-}
diff --git a/lib/widgets/map/step_stats.dart b/lib/widgets/map/step_stats.dart
deleted file mode 100644
index 7d0999af..00000000
--- a/lib/widgets/map/step_stats.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-
-import '../../controllers/walking_controller.dart';
-
-class StepStats extends StatelessWidget {
- const StepStats({super.key});
-
- @override
- Widget build(BuildContext context) {
- var walkController = Get.find();
- return Obx(
- () => Column(
- children: [
- Text(
- '${walkController.getCurrentStep()} 걸음',
- style: TextStyle(
- fontSize: 30,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- SizedBox(
- height: 5,
- ),
- Container(
- padding: EdgeInsets.symmetric(horizontal: 7, vertical: 5),
- decoration: BoxDecoration(
- color: Colors.black,
- borderRadius: BorderRadius.circular(20.0),
- ),
- child: Text(
- '${walkController.getCurrentCalorie()} kcal | ${walkController.getCurrentTravelDistance()}km',
- style:
- TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/widgets/map/visited_user_list_view.dart b/lib/widgets/map/visited_user_list_view.dart
deleted file mode 100644
index 43cb2ea0..00000000
--- a/lib/widgets/map/visited_user_list_view.dart
+++ /dev/null
@@ -1,65 +0,0 @@
-import 'package:flutter/material.dart';
-
-import '../../models/individual_mode_pixel_info.dart';
-
-class VisitedUserListView extends StatelessWidget {
- final List visitList;
-
- VisitedUserListView({super.key, required this.visitList});
-
- @override
- Widget build(BuildContext context) {
- return Expanded(
- child: Container(
- decoration: const BoxDecoration(
- color: Color(0xFF8B8B8B),
- borderRadius: BorderRadius.only(
- topRight: Radius.circular(20),
- topLeft: Radius.circular(20),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
- child: ListView.builder(
- itemCount: visitList.length,
- itemBuilder: (context, int index) {
- return Padding(
- padding: const EdgeInsets.all(5.0),
- child: Row(
- children: [
- ClipOval(
- child: visitList[index].profileImageUrl != null
- ? Image.network(
- visitList[index].profileImageUrl!,
- width: 50,
- height: 50,
- fit: BoxFit.cover,
- )
- : Image.asset(
- 'assets/images/default_profile_image.png',
- width: 50,
- height: 50,
- ),
- ),
- SizedBox(
- width: 20,
- ),
- Text(
- visitList[index].nickname!,
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.bold,
- color: Colors.white,
- ),
- ),
- ],
- ),
- );
- },
- shrinkWrap: true,
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/my_page/pixel_dash_board.dart b/lib/widgets/my_page/pixel_dash_board.dart
index 8a11d824..ef440258 100644
--- a/lib/widgets/my_page/pixel_dash_board.dart
+++ b/lib/widgets/my_page/pixel_dash_board.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
-import '../../controllers/my_page_controller.dart';
+import '../../controllers/map_controller.dart';
import '../../controllers/walking_controller.dart';
import 'pixel_dash_board_widget.dart';
@@ -10,7 +10,7 @@ class PixelDashBoard extends StatelessWidget {
super.key,
});
- final MyPageController myPageController = Get.find();
+ final MapController mapController = Get.find();
final WalkingController walkingController = Get.find();
@override
@@ -21,13 +21,13 @@ class PixelDashBoard extends StatelessWidget {
PixelDashBoardWidget(
textValue: "현재 px",
iconImageUrl: "assets/images/current_pixel_icon.png",
- countValue: myPageController.currentPixelCount,
+ countValue: mapController.currentPixelCount,
),
SizedBox(width: 20),
PixelDashBoardWidget(
textValue: "누적 px",
iconImageUrl: "assets/images/accumulate_pixel_icon.png",
- countValue: myPageController.accumulatePixelCount,
+ countValue: mapController.accumulatePixelCount,
),
],
);
diff --git a/lib/widgets/pixel.dart b/lib/widgets/pixel.dart
index 986bedf5..3f82cc47 100644
--- a/lib/widgets/pixel.dart
+++ b/lib/widgets/pixel.dart
@@ -1,15 +1,16 @@
+import 'dart:math';
+
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
+import '../controllers/bottom_sheet_controller.dart';
import '../controllers/pixel_info_controller.dart';
import '../models/individual_history_pixel.dart';
import '../models/individual_history_pixel_info.dart';
import '../models/individual_mode_pixel.dart';
import '../models/individual_mode_pixel_info.dart';
import '../utils/user_manager.dart';
-import 'map/individual_history_pixel_info_bottom_sheet.dart';
-import 'map/individual_mode_pixel_info_bottom_sheet.dart';
class Pixel extends Polygon {
static const double latPerPixel = 0.000724;
@@ -53,21 +54,17 @@ class Pixel extends Polygon {
topLeftPoint: LatLng(pixel.latitude, pixel.longitude),
),
fillColor: isMyPixel
- ? Colors.blue.withOpacity(0.3)
- : Colors.red.withOpacity(0.3),
- strokeColor: isMyPixel ? Colors.blue : Colors.red,
- strokeWidth: 1,
+ ? Color(0xFF0DF69E)
+ .withOpacity(0.3 + (Random().nextDouble() * (0.6 - 0.3)))
+ : Colors.red.withOpacity(0.3 + (Random().nextDouble() * (0.6 - 0.3))),
+ strokeColor: isMyPixel ? Color(0xFF0DF69E) : Colors.red,
+ strokeWidth: 2,
onTap: (int pixelId) async {
IndividualModePixelInfo pixelInfo =
await Get.find()
.getIndividualModePixelInfo(pixelId);
- Get.bottomSheet(
- IndividualModePixelInfoBottomSheet(pixelInfo: pixelInfo),
- clipBehavior: Clip.hardEdge,
- backgroundColor: Colors.white,
- enterBottomSheetDuration: Duration(milliseconds: 100),
- exitBottomSheetDuration: Duration(milliseconds: 100),
- );
+ Get.find()
+ .showIndividualModePixelInfo(pixelInfo);
},
);
}
@@ -83,23 +80,18 @@ class Pixel extends Polygon {
points: _getRectangleFromLatLng(
topLeftPoint: LatLng(pixel.latitude, pixel.longitude),
),
- fillColor: Colors.blue.withOpacity(0.3),
- strokeColor: Colors.blue,
- strokeWidth: 1,
+ fillColor: Color(0xFF0DF69E)
+ .withOpacity(0.3 + (Random().nextDouble() * (0.6 - 0.3))),
+ strokeColor: Color(0xFF0DF69E),
+ strokeWidth: 2,
onTap: (int pixelId) async {
IndividualHistoryPixelInfo pixelInfo =
await Get.find().getIndividualHistoryPixelInfo(
pixelId,
UserManager().getUserId()!,
);
-
- Get.bottomSheet(
- IndividualHistoryPixelInfoBottomSheet(pixelInfo: pixelInfo),
- clipBehavior: Clip.hardEdge,
- backgroundColor: Colors.white,
- enterBottomSheetDuration: Duration(milliseconds: 100),
- exitBottomSheetDuration: Duration(milliseconds: 100),
- );
+ Get.find()
+ .showIndividualHistoryPixelInfo(pixelInfo);
},
);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index e71c1fbd..0c39417a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -53,4 +53,4 @@ flutter:
flutter_native_splash:
color: "#000000"
- image: assets/images/groundflip_logo.png
\ No newline at end of file
+ image: "assets/images/ground_flip_app_icon.png"
\ No newline at end of file