diff --git a/lib/auth_gate.dart b/lib/auth_gate.dart index 3f49fbc..1897ce5 100644 --- a/lib/auth_gate.dart +++ b/lib/auth_gate.dart @@ -1,7 +1,6 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutterfire_ui/auth.dart'; -import 'package:firebase_database/firebase_database.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'home.dart'; @@ -12,11 +11,7 @@ Future addUser(String? name, String uid) async { final CollectionReference users = FirebaseFirestore.instance.collection('users'); return await users - .add({ - 'name': name, - 'uid': uid, - 'swipes': 0, - }) + .add({'name': name, 'uid': uid, 'swipes': 0, 'seller-id': ""}) .then((value) => print("")) .catchError((error) => print("ERROR ADDING DATA: $error")); } @@ -77,7 +72,7 @@ class AuthGate extends StatelessWidget { final name = user.displayName; final uid = user.uid; - addUser(name, uid); + addUser("TOAA", uid); } } diff --git a/lib/buy.dart b/lib/buy.dart index 535e499..883fb04 100644 --- a/lib/buy.dart +++ b/lib/buy.dart @@ -39,10 +39,20 @@ final List> eatingLocations = [ 'Kilmer\'s Market', 'Sbarro\'s', ], - ['Neilson Dining Hall', 'Cook Cafe', 'Douglass Cafe', 'Harvest INFH', 'Red Pine Pizza'] + [ + 'Neilson Dining Hall', + 'Cook Cafe', + 'Douglass Cafe', + 'Harvest INFH', + 'Red Pine Pizza' + ] ]; final List colorCodes = [600, 500, 100]; -final List timeBgAssets = ['assets/daytime_swipe.jpg', 'assets/afternoon_swipe.jpg', 'assets/nighttime_swipe.jpg']; +final List timeBgAssets = [ + 'assets/daytime_swipe.jpg', + 'assets/afternoon_swipe.jpg', + 'assets/nighttime_swipe.jpg' +]; class BuyScreen extends StatefulWidget { const BuyScreen({Key? key}) : super(key: key); @@ -62,6 +72,17 @@ class _BuyScreenState extends State { return Scaffold( body: Column( children: [ + Row(children: [ + Container( + padding: EdgeInsets.only(left: 10, top: 20), + child: Text( + "Campuses", + style: TextStyle( + fontSize: 30, + ), + ), + ) + ]), Expanded( child: Center( child: GridView.count( @@ -81,7 +102,8 @@ class _BuyScreenState extends State { }); }, child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: + const BorderRadius.all(Radius.circular(25)), child: Column( children: [ Expanded( @@ -111,7 +133,8 @@ class _BuyScreenState extends State { }); }, child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: + const BorderRadius.all(Radius.circular(25)), child: Column( children: [ Expanded( @@ -141,7 +164,8 @@ class _BuyScreenState extends State { }); }, child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: + const BorderRadius.all(Radius.circular(25)), child: Column( children: [ Expanded( @@ -171,7 +195,8 @@ class _BuyScreenState extends State { }); }, child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: + const BorderRadius.all(Radius.circular(25)), child: Column( children: [ Expanded( @@ -213,6 +238,17 @@ class _BuyScreenState extends State { }, child: const Text("Choose A Different Location")), ), + Row(children: [ + Container( + padding: EdgeInsets.only(left: 10, bottom: 10), + child: Text( + "Swipe Locations", + style: TextStyle( + fontSize: 30, + ), + ), + ) + ]), Expanded( child: ListView.builder( physics: const BouncingScrollPhysics(), @@ -220,8 +256,13 @@ class _BuyScreenState extends State { padding: EdgeInsets.fromLTRB(10, 0, 10, 0), itemBuilder: (BuildContext context, int index) { return Container( - margin: const EdgeInsets.all(4), - decoration: BoxDecoration(border: Border.all(color: CustomMaterialColor(240, 240, 240).mdColor, width: 2), borderRadius: const BorderRadius.all(Radius.circular(10))), + margin: const EdgeInsets.fromLTRB(4, 4, 4, 8), + decoration: BoxDecoration( + border: Border.all( + color: CustomMaterialColor(240, 240, 240).mdColor, + width: 2), + borderRadius: + const BorderRadius.all(Radius.circular(10))), child: InkWell( onTap: () { setState(() { @@ -230,20 +271,24 @@ class _BuyScreenState extends State { }); }, child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(10)), + borderRadius: + const BorderRadius.all(Radius.circular(10)), child: Container( height: 120, decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.cover, - image: AssetImage("assets/" + nameToAsset[_diningOptions.elementAt(index)]!), + image: AssetImage("assets/" + + nameToAsset[ + _diningOptions.elementAt(index)]!), )), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: Container( padding: const EdgeInsets.only(right: 40), alignment: Alignment.bottomRight, - margin: const EdgeInsets.only(top: 4, left: 20, bottom: 4), + margin: const EdgeInsets.only( + top: 4, left: 20, bottom: 4), child: Text( _diningOptions[index], overflow: TextOverflow.ellipsis, @@ -265,21 +310,38 @@ class _BuyScreenState extends State { children: [ Padding( padding: EdgeInsets.fromLTRB(0, 30, 0, 0), - child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Flexible( - flex: 8, - fit: FlexFit.tight, - child: ElevatedButton( - onPressed: () { - setState(() { - _currentState = CampusState.campuses; - }); - }, - child: const Text("Choose A Different Location")), - ), - Flexible(flex: 2, child: ElevatedButton(onPressed: () {}, child: const Icon(Icons.filter_list))) - ]), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + flex: 8, + fit: FlexFit.tight, + child: ElevatedButton( + onPressed: () { + setState(() { + _currentState = CampusState.campuses; + }); + }, + child: const Text("Choose A Different Location")), + ), + Flexible( + flex: 2, + child: ElevatedButton( + onPressed: () {}, + child: const Icon(Icons.filter_list))) + ]), ), + Row(children: [ + Container( + padding: EdgeInsets.only(left: 10, bottom: 10), + child: Text( + "Available Swipes", + style: TextStyle( + fontSize: 30, + ), + ), + ) + ]), Expanded( child: ListView.builder( physics: const BouncingScrollPhysics(), @@ -348,15 +410,23 @@ class _BuyScreenState extends State { // // ); }, child: Container( - decoration: BoxDecoration(border: Border.all(color: CustomMaterialColor(240, 240, 240).mdColor, width: 2), borderRadius: const BorderRadius.all(Radius.circular(10))), + decoration: BoxDecoration( + border: Border.all( + color: CustomMaterialColor(240, 240, 240) + .mdColor, + width: 2), + borderRadius: + const BorderRadius.all(Radius.circular(10))), margin: const EdgeInsets.all(4), height: 120, child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(10)), + borderRadius: + const BorderRadius.all(Radius.circular(10)), child: Container( decoration: BoxDecoration( image: DecorationImage( - colorFilter: const ColorFilter.mode(Colors.black26, BlendMode.darken), + colorFilter: const ColorFilter.mode( + Colors.black26, BlendMode.darken), fit: BoxFit.cover, image: AssetImage(timeBgAssets[index % 3]), )), @@ -369,15 +439,18 @@ class _BuyScreenState extends State { child: Container( padding: EdgeInsets.only(left: 20), // color: Colors.red, - margin: const EdgeInsets.only(top: 4, left: 4, bottom: 4), + margin: const EdgeInsets.only( + top: 4, left: 4, bottom: 4), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( entries[index % entries.length], textAlign: TextAlign.start, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 20), + style: + const TextStyle(fontSize: 20), ), Row( children: const [ @@ -385,18 +458,22 @@ class _BuyScreenState extends State { Icon(Icons.star, size: 16), Icon(Icons.star, size: 16), Icon(Icons.star_half, size: 16), - Icon(Icons.star_border, size: 16), + Icon(Icons.star_border, + size: 16), ], ), Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: + MainAxisAlignment.end, children: const [ Text( '88:88PM - 88:88PM', textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 16), + overflow: + TextOverflow.ellipsis, + style: + TextStyle(fontSize: 16), ), ], ), @@ -545,7 +622,7 @@ class _BuyScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ElevatedButton( + ElevatedButton( onPressed: () {}, child: Text( "Cancel", @@ -597,7 +674,8 @@ class _TransactionDetailsState extends State { ); } } - class CustomMaterialColor { + +class CustomMaterialColor { final int r; final int g; final int b; diff --git a/lib/main.dart b/lib/main.dart index 6fd6ee1..fbb9a37 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,9 +9,10 @@ import 'auth_gate.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - Stripe.publishableKey = "pk_test_51MfY7PFVdcWv896FKvDhgKabYeDq4AnoFcWxCAg4hquj6TBAsN0kznXPVyKA7M1pMq5PsieGQwsx6QY5ld5ZQzJ500rVCMPPXp"; - -runApp(const MyApp()); + Stripe.publishableKey = + "pk_test_51MfY7PFVdcWv896FKvDhgKabYeDq4AnoFcWxCAg4hquj6TBAsN0kznXPVyKA7M1pMq5PsieGQwsx6QY5ld5ZQzJ500rVCMPPXp"; + + runApp(const MyApp()); } class MyApp extends StatelessWidget { @@ -20,23 +21,29 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( - primarySwatch: CustomMaterialColor(205, 0, 48).mdColor, - scaffoldBackgroundColor: Colors.black87, - fontFamily: GoogleFonts.figtree().fontFamily, - textTheme: Theme.of(context).textTheme.apply( - bodyColor: CustomMaterialColor(240, 240, 240).mdColor, - displayColor: CustomMaterialColor(240, 240, 240).mdColor, - ), - textButtonTheme: TextButtonThemeData( - style: ButtonStyle( - textStyle: MaterialStatePropertyAll(TextStyle( - color: CustomMaterialColor(240, 240, 240).mdColor))))), + primarySwatch: CustomMaterialColor(205, 0, 48).mdColor, + scaffoldBackgroundColor: Colors.black87, + fontFamily: GoogleFonts.figtree().fontFamily, + textTheme: Theme.of(context).textTheme.apply( + bodyColor: CustomMaterialColor(240, 240, 240).mdColor, + displayColor: CustomMaterialColor(240, 240, 240).mdColor, + ), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + textStyle: MaterialStatePropertyAll(TextStyle( + color: CustomMaterialColor(240, 240, 240).mdColor)))), + checkboxTheme: CheckboxThemeData( + fillColor: MaterialStatePropertyAll( + CustomMaterialColor(240, 240, 240).mdColor), + checkColor: MaterialStatePropertyAll(Colors.black), + ), + ), home: const AuthGate(), ); } } - class CustomMaterialColor { +class CustomMaterialColor { final int r; final int g; final int b; diff --git a/lib/meetings.dart b/lib/meetings.dart index 0782c5f..74666b7 100644 --- a/lib/meetings.dart +++ b/lib/meetings.dart @@ -17,11 +17,11 @@ class PriceRange { } class Seller implements Comparable { - String name = ""; + String? name = "Unknown Seller"; String uid = ""; List location; TimeRange availableTime; - int price; + double price; Seller(this.name, this.uid, this.location, this.availableTime, this.price); @@ -32,7 +32,7 @@ class Seller implements Comparable { @override int compareTo(Seller other) { - return name.compareTo(other.name); + return price.compareTo(other.price); } } @@ -44,6 +44,34 @@ class Filter { Filter(this.locations, this.price, this.meetingTime); } +List fetchNSellers(int n) { + List sellers = List.empty(growable: true); + CollectionReference users = FirebaseFirestore.instance.collection('sellers'); + users.get().then((value) => () { + for (var doc in value.docs) { + sellers.add(Seller(doc["name"], doc["uid"], doc["location"], + TimeRange(doc["start-time"], doc["end-time"]), doc["price"])); + } + }); + return sellers; +} + +void addSeller(Seller seller) async { + final CollectionReference sellers = + FirebaseFirestore.instance.collection('sellers'); + return await sellers + .add({ + 'name': seller.name, + 'uid': seller.uid, + 'price': seller.price, + 'start-time': seller.availableTime.startTime, + 'end-time': seller.availableTime.endTime, + 'location': seller.location, + }) + .then((value) => print("")) + .catchError((error) => print("ERROR ADDING DATA: $error")); +} + Future> getSellers(Filter filter) async { CollectionReference users = FirebaseFirestore.instance.collection('sellers'); List sellers = List.empty(growable: true); @@ -55,9 +83,26 @@ Future> getSellers(Filter filter) async { isLessThanOrEqualTo: filter.price?.high); final QuerySnapshot snapshot = await query.get(); - for (var doc in snapshot.docs) { - sellers.add(Seller(doc["name"], doc["uid"], doc["location"], - TimeRange(doc["start-time"], doc["end-time"]), doc["price"])); + final startTime = filter.meetingTime?.endTime; + final endTime = filter.meetingTime?.startTime; + + if (startTime != null && endTime != null) { + var docs = snapshot.docs + .where((element) => + startTime.compareTo(element["start-time"]) > 0 || + element["start-time"] == (startTime)) + .where((element) => + endTime.compareTo(element["end-time"]) < 0 || + element["end-time"] == (endTime)); + for (var doc in docs) { + sellers.add(Seller(doc["name"], doc["uid"], doc["location"], + TimeRange(doc["start-time"], doc["end-time"]), doc["price"])); + } + } else { + for (var doc in snapshot.docs) { + sellers.add(Seller(doc["name"], doc["uid"], doc["location"], + TimeRange(doc["start-time"], doc["end-time"]), doc["price"])); + } } return sellers; } diff --git a/lib/sell.dart b/lib/sell.dart index 7e9b5f9..8c04935 100644 --- a/lib/sell.dart +++ b/lib/sell.dart @@ -1,7 +1,14 @@ // ignore_for_file: prefer_const_constructors +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'package:currency_text_input_formatter/currency_text_input_formatter.dart'; +import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; +import 'package:ruswipeshare/auth_gate.dart'; +import 'package:intl/intl.dart'; import 'package:ruswipeshare/meetings.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; class SellScreen extends StatefulWidget { const SellScreen({Key? key}) : super(key: key); @@ -24,28 +31,47 @@ Map values = { }; class _SellScreenState extends State { + DateFormat dateFormat = DateFormat("HH:mm a"); String startTime = 'Start Time'; DateTime startTimeTime = DateTime.now(); String endTime = 'End Time'; DateTime endTimeTime = DateTime.now(); bool? is24HoursFormat; + double price = 0; + final priceController = TextEditingController(); @override Widget build(BuildContext context) { bool is24HoursFormat = MediaQuery.of(context).alwaysUse24HourFormat; return Scaffold( - body: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + resizeToAvoidBottomInset: true, + body: Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), + child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Icon(Icons.store_mall_directory, color: Colors.red), - Text('Place'), + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.store_mall_directory, + color: Colors.red, + size: 40, + ), + Text( + 'Place', + style: TextStyle( + fontSize: 30, + ), + ), + ], + )) ], ), ConstrainedBox( constraints: const BoxConstraints.expand(height: 250), child: ListView.builder( + physics: BouncingScrollPhysics(), itemCount: values.length, itemBuilder: (context, index) => CheckboxListTile( title: Text(values.keys.elementAt(index)), @@ -90,49 +116,49 @@ class _SellScreenState extends State { children: [ ElevatedButton( onPressed: () async { - final TimeOfDay? picked = await showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime(startTimeTime), - ); - if (picked != null && - picked != TimeOfDay.fromDateTime(startTimeTime)) { + DatePicker.showDateTimePicker(context, showTitleActions: true, minTime: DateTime.now(), onChanged: (date) { + print('change $date'); + }, onConfirm: (date) { setState(() { - startTimeTime = DateTime.fromMicrosecondsSinceEpoch( - picked.hour * 60 * 60 * 1000000 + - picked.minute * 60 * 1000000, - isUtc: true); - startTime = startTimeTime.hour.toString() + - ":" + - startTimeTime.minute.toString() + - ((is24HoursFormat) - ? "" - : ((startTimeTime.hour > 12) ? "PM" : "AM")); + startTimeTime = date; + startTime = dateFormat.format(date); + print('confirm $date'); }); - } + }, currentTime: DateTime.now(), locale: LocaleType.en); + // final TimeOfDay? picked = await showTimePicker( + // context: context, + // initialTime: TimeOfDay.fromDateTime(startTimeTime), + // ); + // if (picked != null && picked != TimeOfDay.fromDateTime(startTimeTime)) { + // setState(() { + // startTimeTime = DateTime.fromMicrosecondsSinceEpoch(picked.hour * 60 * 60 * 1000000 + picked.minute * 60 * 1000000, isUtc: true); + // startTime = startTimeTime.hour.toString() + ":" + startTimeTime.minute.toString() + ((is24HoursFormat) ? "" : ((startTimeTime.hour > 12) ? "PM" : "AM")); + // }); + // } }, child: const Text('Select Start Time'), ), ElevatedButton( onPressed: () async { - final TimeOfDay? picked = await showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime(endTimeTime), - ); - if (picked != null && - picked != TimeOfDay.fromDateTime(endTimeTime)) { + DatePicker.showDateTimePicker(context, showTitleActions: true, minTime: DateTime.now(), onChanged: (date) { + print('change $date'); + }, onConfirm: (date) { setState(() { - endTimeTime = DateTime.fromMicrosecondsSinceEpoch( - picked.hour * 60 * 60 * 1000000 + - picked.minute * 60 * 1000000, - isUtc: true); - endTime = endTimeTime.hour.toString() + - ":" + - endTimeTime.minute.toString() + - ((is24HoursFormat) - ? "" - : ((endTimeTime.hour > 12) ? "PM" : "AM")); + endTimeTime = date; + endTime = dateFormat.format(date); + print('confirm $date'); }); - } + }, currentTime: DateTime.now(), locale: LocaleType.en); + // final TimeOfDay? picked = await showTimePicker( + // context: context, + // initialTime: TimeOfDay.fromDateTime(endTimeTime), + // ); + // if (picked != null && picked != TimeOfDay.fromDateTime(endTimeTime)) { + // setState(() { + // endTimeTime = DateTime.fromMicrosecondsSinceEpoch(picked.hour * 60 * 60 * 1000000 + picked.minute * 60 * 1000000, isUtc: true); + // endTime = endTimeTime.hour.toString() + ":" + endTimeTime.minute.toString() + ((is24HoursFormat) ? "" : ((endTimeTime.hour > 12) ? "PM" : "AM")); + // }); + // } }, child: const Text('Select End Time'), ), @@ -146,23 +172,37 @@ class _SellScreenState extends State { ], ), SizedBox( - width: 150, - child: TextField( - decoration: InputDecoration( - border: OutlineInputBorder(), - hintText: 'Max price', - ), - ), + width: 150, + child: TextField( + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white, fontSize: 30), + controller: priceController, + decoration: InputDecoration( + border: OutlineInputBorder(), + hintText: 'Price', + hintStyle: TextStyle(color: Colors.white24, fontSize: 30), ), - ElevatedButton( - onPressed: () {}, - style: ButtonStyle( - backgroundColor: - MaterialStateColor.resolveWith((states) => Colors.blue), ), - child: const Text('Next'), ), - ], + ElevatedButton( + onPressed: () { + List locations = []; + User? user = auth.currentUser; + values.forEach((key, value) { + if (value == true) locations.add(key); + }); + if (user != null) { + Seller seller = Seller(user.displayName, user.uid, locations, TimeRange(Timestamp.fromDate(startTimeTime), Timestamp.fromDate(endTimeTime)), double.parse(priceController.text)); + addSeller(seller); + } + }, + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith((states) => Colors.blue), + ), + child: const Text('Submit Sell Request'), + ), + ]), ), ); } diff --git a/pubspec.lock b/pubspec.lock index bfd356a..0b83bc5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,7 +50,7 @@ packages: source: hosted version: "1.1.1" cloud_firestore: - dependency: transitive + dependency: "direct main" description: name: cloud_firestore sha256: "65f148d9f5b4f389320abb45847120cf5e46094c1a8cbc64934ffc1e29688596" @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + currency_text_input_formatter: + dependency: "direct main" + description: + name: currency_text_input_formatter + sha256: "9ff3299b37e73ba76a5a40c645ecd234acba7d54bc8e34f60aeedec2c39b0e6e" + url: "https://pub.dev" + source: hosted + version: "2.1.9" desktop_webview_auth: dependency: transitive description: @@ -246,6 +254,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.5" + flutter_datetime_picker: + dependency: "direct main" + description: + name: flutter_datetime_picker + sha256: "8e695c63c769350e541951227c2775190ec73ceda774a315b1dc9a99d5facfe5" + url: "https://pub.dev" + source: hosted + version: "1.5.1" flutter_dotenv: dependency: "direct main" description: @@ -446,7 +462,7 @@ packages: source: hosted version: "4.0.2" intl: - dependency: transitive + dependency: "direct overridden" description: name: intl sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" diff --git a/pubspec.yaml b/pubspec.yaml index bee0926..6231c27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,9 +45,12 @@ dependencies: flutter_stripe: any material_dialogs: any google_fonts: ^4.0.3 + cloud_firestore: any + currency_text_input_formatter: ^2.1.9 http: any webview_flutter: any url_launcher: ^6.1.7 + flutter_datetime_picker: ^1.5.1 dev_dependencies: flutter_test: @@ -100,3 +103,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages +dependency_overrides: + intl: 0.17.0 \ No newline at end of file