session crud #5
@ -1,3 +0,0 @@
|
|||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
|
|
11
lib/helpers/date_time_helpers.dart
Normal file
11
lib/helpers/date_time_helpers.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
|
||||||
|
|
||||||
|
String formattedTime(int timeInSecond) {
|
||||||
|
int sec = timeInSecond % 60;
|
||||||
|
int min = (timeInSecond / 60).floor();
|
||||||
|
String minute = min.toString().length <= 1 ? "0$min" : "$min";
|
||||||
|
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
|
||||||
|
return "$minute:$second";
|
||||||
|
}
|
@ -100,6 +100,9 @@ class _AppState extends State<App> {
|
|||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||||
|
),
|
||||||
context: context,
|
context: context,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
@ -4,6 +4,7 @@ import 'package:sendtrain/daos/activities_dao.dart';
|
|||||||
import 'package:sendtrain/daos/media_items_dao.dart';
|
import 'package:sendtrain/daos/media_items_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
|
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||||
import 'package:sendtrain/helpers/media_helpers.dart';
|
import 'package:sendtrain/helpers/media_helpers.dart';
|
||||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||||
import 'package:sendtrain/widgets/activities/activity_view.dart';
|
import 'package:sendtrain/widgets/activities/activity_view.dart';
|
||||||
@ -21,18 +22,10 @@ class ActivityCard extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ActivityCardState extends State<ActivityCard> {
|
class ActivityCardState extends State<ActivityCard> {
|
||||||
String formattedTime(int timeInSecond) {
|
|
||||||
int sec = timeInSecond % 60;
|
|
||||||
int min = (timeInSecond / 60).floor();
|
|
||||||
String minute = min.toString().length <= 1 ? "0$min" : "$min";
|
|
||||||
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
|
|
||||||
return "$minute:$second";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ActivityTimerModel atm =
|
final ActivityTimerModel atm =
|
||||||
Provider.of<ActivityTimerModel>(context, listen: false);
|
Provider.of<ActivityTimerModel>(context);
|
||||||
|
|
||||||
return FutureBuilder<List<MediaItem>>(
|
return FutureBuilder<List<MediaItem>>(
|
||||||
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
||||||
|
@ -20,7 +20,8 @@ Future showGenericDialog(dynamic object, BuildContext parentContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showRemovalDialog(title, content, context, dao, session) {
|
Future showRemovalDialog(String title, String content, BuildContext context,
|
||||||
|
dynamic dao, dynamic object) {
|
||||||
return showAdaptiveDialog(
|
return showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => AlertDialog(
|
builder: (BuildContext context) => AlertDialog(
|
||||||
@ -34,7 +35,7 @@ Future showRemovalDialog(title, content, context, dao, session) {
|
|||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => {dao.remove(session), Navigator.pop(context, 'OK')},
|
onPressed: () => {dao.remove(object), Navigator.pop(context, 'OK')},
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum SizeAxis { width, height }
|
||||||
|
|
||||||
class CardImage extends StatelessWidget {
|
class CardImage extends StatelessWidget {
|
||||||
const CardImage({super.key, required this.image});
|
const CardImage({super.key, required this.image, this.padding, this.size});
|
||||||
|
|
||||||
final ImageProvider<Object> image;
|
final ImageProvider<Object> image;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
|
final Map<SizeAxis, double>? size;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
padding: padding ?? const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 60,
|
width: size?[SizeAxis.width] ?? 60,
|
||||||
|
height: size?[SizeAxis.height] ?? 60,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
@ -1,7 +1,29 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class Suggestion {
|
||||||
|
final String placeId;
|
||||||
|
final String description;
|
||||||
|
final String address;
|
||||||
|
|
||||||
|
Suggestion(this.placeId, this.description, this.address);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Suggestion(description: $description, placeId: $placeId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
Map toJson() => {
|
||||||
|
'placeId': placeId,
|
||||||
|
'name': description,
|
||||||
|
'address': address,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class FormSearchInput extends StatefulWidget {
|
class FormSearchInput extends StatefulWidget {
|
||||||
const FormSearchInput({super.key, required this.sessionController});
|
const FormSearchInput({super.key, required this.sessionController});
|
||||||
@ -26,7 +48,10 @@ class _FormSearchInputState extends State<FormSearchInput> {
|
|||||||
_currentQuery = query;
|
_currentQuery = query;
|
||||||
|
|
||||||
// In a real application, there should be some error handling here.
|
// In a real application, there should be some error handling here.
|
||||||
final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
|
// final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
|
||||||
|
if (query.isNotEmpty) {
|
||||||
|
final List<Suggestion>? suggestions =
|
||||||
|
await fetchSuggestions(_currentQuery!, 'en');
|
||||||
|
|
||||||
// If another search happened after this one, throw away these options.
|
// If another search happened after this one, throw away these options.
|
||||||
if (_currentQuery != query) {
|
if (_currentQuery != query) {
|
||||||
@ -34,7 +59,44 @@ class _FormSearchInputState extends State<FormSearchInput> {
|
|||||||
}
|
}
|
||||||
_currentQuery = null;
|
_currentQuery = null;
|
||||||
|
|
||||||
return options;
|
return suggestions?.map((suggestion) => json.encode(suggestion));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final sessionToken = Uuid().v4();
|
||||||
|
final apiKey = "AIzaSyBCjMCEAyyNVpsnVYvZj6VL1mmB98Vd6AE";
|
||||||
|
final client = Client();
|
||||||
|
|
||||||
|
Future<List<Suggestion>?> fetchSuggestions(String input, String lang) async {
|
||||||
|
var headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Goog-Api-Key': apiKey,
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
'X-Goog-FieldMask': 'places.displayName,places.id,places.formattedAddress'
|
||||||
|
};
|
||||||
|
var request = Request('POST',
|
||||||
|
Uri.parse('https://places.googleapis.com/v1/places:searchText'));
|
||||||
|
request.body = json.encode({"textQuery": input});
|
||||||
|
request.headers.addAll(headers);
|
||||||
|
|
||||||
|
StreamedResponse response = await request.send();
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final result = json.decode(await response.stream.bytesToString());
|
||||||
|
|
||||||
|
if (result.isNotEmpty) {
|
||||||
|
return result['places']
|
||||||
|
.map<Suggestion>((p) => Suggestion(
|
||||||
|
p['id'], p['displayName']['text'], p['formattedAddress']))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception(response.reasonPhrase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -65,11 +127,12 @@ class _FormSearchInputState extends State<FormSearchInput> {
|
|||||||
_lastOptions = List<ListTile>.generate(options.length, (int index) {
|
_lastOptions = List<ListTile>.generate(options.length, (int index) {
|
||||||
final String item = options[index];
|
final String item = options[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(item),
|
title: Text(json.decode(item)['name']),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (item.isNotEmpty) {
|
if (item.isNotEmpty) {
|
||||||
widget.sessionController.text = item;
|
widget.sessionController.text = json.decode(item)['name'];
|
||||||
}
|
}
|
||||||
|
client.close();
|
||||||
controller.closeView(item);
|
controller.closeView(item);
|
||||||
// debugPrint('You just selected $item');
|
// debugPrint('You just selected $item');
|
||||||
// Navigator.of(context).pop();
|
// Navigator.of(context).pop();
|
||||||
@ -82,28 +145,7 @@ class _FormSearchInputState extends State<FormSearchInput> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Duration fakeAPIDuration = Duration(seconds: 1);
|
|
||||||
const Duration debounceDuration = Duration(milliseconds: 500);
|
const Duration debounceDuration = Duration(milliseconds: 500);
|
||||||
|
|
||||||
class _FakeAPI {
|
|
||||||
static const List<String> _kOptions = <String>[
|
|
||||||
'aardvark',
|
|
||||||
'bobcat',
|
|
||||||
'chameleon',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Searches the options, but injects a fake "network" delay.
|
|
||||||
static Future<Iterable<String>> search(String query) async {
|
|
||||||
await Future<void>.delayed(fakeAPIDuration); // Fake 1 second delay.
|
|
||||||
if (query == '') {
|
|
||||||
return const Iterable<String>.empty();
|
|
||||||
}
|
|
||||||
return _kOptions.where((String option) {
|
|
||||||
return option.contains(query.toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef _Debounceable<S, T> = Future<S?> Function(T parameter);
|
typedef _Debounceable<S, T> = Future<S?> Function(T parameter);
|
||||||
|
|
||||||
/// Returns a new function that is a debounced version of the given function.
|
/// Returns a new function that is a debounced version of the given function.
|
||||||
|
@ -50,42 +50,42 @@ class MediaCard extends StatelessWidget {
|
|||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
shadowColor: const Color.fromARGB(0, 255, 255, 255),
|
shadowColor: const Color.fromARGB(0, 255, 255, 255),
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => showDialog<String>(
|
onPressed: () => showModalBottomSheet<void>(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||||
|
),
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => Dialog.fullscreen(
|
showDragHandle: true,
|
||||||
child: Padding(
|
isScrollControlled: true,
|
||||||
padding: const EdgeInsets.all(8.0),
|
useSafeArea: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
mediaItem(media),
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: mediaItem(media),
|
||||||
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
Text(
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
||||||
|
child: Text(
|
||||||
media.description,
|
media.description,
|
||||||
style: const TextStyle(fontSize: 20),
|
style: const TextStyle(fontSize: 20),
|
||||||
),
|
)),
|
||||||
const Divider(
|
const Divider(
|
||||||
indent: 20,
|
indent: 20,
|
||||||
endIndent: 20,
|
endIndent: 20,
|
||||||
color: Colors.deepPurple,
|
)
|
||||||
),
|
]));
|
||||||
// const Text(
|
// const Text(
|
||||||
// 'Comments',
|
// 'Comments',
|
||||||
// style: TextStyle(fontSize: 20),
|
// style: TextStyle(fontSize: 20),
|
||||||
// ),
|
// ),
|
||||||
const SizedBox(height: 15),
|
}),
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: const Text('Close'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const ListTile(
|
child: const ListTile(
|
||||||
title: Text(''),
|
title: Text(''),
|
||||||
))));
|
))));
|
||||||
|
@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
import 'package:sendtrain/helpers/date_helpers.dart';
|
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||||
import 'package:sendtrain/helpers/media_helpers.dart';
|
import 'package:sendtrain/helpers/media_helpers.dart';
|
||||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/card_content.dart';
|
import 'package:sendtrain/widgets/generic/elements/card_content.dart';
|
||||||
@ -22,6 +22,15 @@ class SessionCardFull extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SessionCardFullState extends State<SessionCardFull> {
|
class _SessionCardFullState extends State<SessionCardFull> {
|
||||||
|
|
||||||
|
String sessionTitle(Session session) {
|
||||||
|
String title = session.title.toTitleCase();
|
||||||
|
|
||||||
|
if (session.address != null) title = "$title @ ${session.address}";
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Session session = widget.session;
|
final Session session = widget.session;
|
||||||
@ -42,8 +51,10 @@ class _SessionCardFullState extends State<SessionCardFull> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: EdgeInsets.only(left: 8),
|
contentPadding: EdgeInsets.only(left: 8),
|
||||||
leading: CardImage(image: findMediaByType(mediaItems, 'image')),
|
leading: CardImage(
|
||||||
title: Text(maxLines: 1, session.title.toTitleCase()),
|
image: findMediaByType(mediaItems, 'image'),
|
||||||
|
padding: EdgeInsets.only(left: 5, top: 5)),
|
||||||
|
title: Text(maxLines: 1, sessionTitle(session)),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
maxLines: 1, dateFormat.format(session.date as DateTime)),
|
maxLines: 1, dateFormat.format(session.date as DateTime)),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
import 'package:sendtrain/helpers/date_helpers.dart';
|
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||||
import 'package:sendtrain/helpers/media_helpers.dart';
|
import 'package:sendtrain/helpers/media_helpers.dart';
|
||||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
||||||
|
@ -46,6 +46,8 @@ dependencies:
|
|||||||
drift_flutter: ^0.2.2
|
drift_flutter: ^0.2.2
|
||||||
map_location_picker: ^1.2.7
|
map_location_picker: ^1.2.7
|
||||||
file_picker: ^8.1.7
|
file_picker: ^8.1.7
|
||||||
|
http: ^1.2.2
|
||||||
|
uuid: ^4.5.1
|
||||||
|
|
||||||
flutter_launcher_name:
|
flutter_launcher_name:
|
||||||
name: "SendTrain"
|
name: "SendTrain"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user