session crud #5
1
android/app/proguard-rules.pro
vendored
Normal file
1
android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1 @@
|
||||
-keep class androidx.lifecycle.DefaultLifecycleObserver
|
@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-feature android:name="android.hardware.location.network" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
|
||||
<application
|
||||
android:label="sendtrain"
|
||||
android:name="${applicationName}"
|
||||
@ -21,8 +23,8 @@
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
@ -31,6 +33,8 @@
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<meta-data android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyBCjMCEAyyNVpsnVYvZj6VL1mmB98Vd6AE" />
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
@ -38,8 +42,9 @@
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
import GoogleMaps
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@ -7,6 +8,7 @@ import Flutter
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GMSServices.provideAPIKey("AIzaSyBCjMCEAyyNVpsnVYvZj6VL1mmB98Vd6AE")
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
@ -2,6 +2,13 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>The Photo library is used when selecting a photo to upload as media for a Session</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
@ -6,16 +6,31 @@ import 'package:sendtrain/screens/activities_screen.dart';
|
||||
import 'package:sendtrain/screens/sessions_screen.dart';
|
||||
// ignore: unused_import
|
||||
import 'package:sendtrain/database/seed.dart';
|
||||
import 'package:sendtrain/widgets/session_creator.dart';
|
||||
import 'package:sendtrain/widgets/sessions/session_creator.dart';
|
||||
|
||||
class SendTrain extends StatelessWidget {
|
||||
const SendTrain({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData themeData = ThemeData.dark(useMaterial3: true);
|
||||
return MaterialApp(
|
||||
title: "Sendtrain",
|
||||
theme: ThemeData.dark(useMaterial3: true),
|
||||
theme: themeData.copyWith(
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12))),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
fillColor: themeData.colorScheme.surface,
|
||||
),
|
||||
),
|
||||
home: const App());
|
||||
}
|
||||
}
|
||||
@ -84,16 +99,18 @@ class _AppState extends State<App> {
|
||||
]),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
showAdaptiveDialog(
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
builder: (BuildContext context) => SessionCreator(),
|
||||
barrierDismissible: true,
|
||||
barrierLabel: '',
|
||||
context: context);
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
builder: (BuildContext context) {
|
||||
return SessionCreator();
|
||||
});
|
||||
},
|
||||
label: const Text('New Session'),
|
||||
icon: const Icon(Icons.add_chart),
|
||||
backgroundColor: Colors.deepPurple,
|
||||
// backgroundColor: Colors.deepPurple,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/activity_type_filter.dart';
|
||||
import 'activity_type_filter.dart';
|
||||
|
||||
class ActivitiesHeader extends StatefulWidget {
|
||||
const ActivitiesHeader({super.key});
|
@ -5,7 +5,7 @@ import 'package:sendtrain/daos/media_items_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||
import 'package:sendtrain/widgets/activity_view.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_view.dart';
|
||||
|
||||
class ActivityCard extends StatefulWidget {
|
||||
final Activity activity;
|
@ -5,10 +5,10 @@ import 'package:sendtrain/daos/actions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||
import 'package:sendtrain/widgets/activity_action_view.dart';
|
||||
import 'package:sendtrain/widgets/activity_view_categories.dart';
|
||||
import 'package:sendtrain/widgets/activity_view_media.dart';
|
||||
import 'package:sendtrain/widgets/activity_view_types.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_action_view.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_view_categories.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_view_media.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_view_types.dart';
|
||||
|
||||
class ActivityView extends StatefulWidget {
|
||||
const ActivityView(
|
46
lib/widgets/builders/dialogs.dart
Normal file
46
lib/widgets/builders/dialogs.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
||||
|
||||
Future showSessionDialog(Session session, BuildContext parentContext) {
|
||||
return showGeneralDialog(
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
transitionDuration: const Duration(milliseconds: 220),
|
||||
transitionBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
Animation<Offset> custom = Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0), end: const Offset(0.0, 0.0))
|
||||
.animate(animation);
|
||||
return SlideTransition(
|
||||
position: custom,
|
||||
child: Dialog.fullscreen(child: SessionView(session: session)));
|
||||
},
|
||||
barrierDismissible: true,
|
||||
barrierLabel: '',
|
||||
context: parentContext,
|
||||
pageBuilder: (context, animation1, animation2) {
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
||||
Future showRemovalDialog(title, content, context, dao, session) {
|
||||
return showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => {
|
||||
Navigator.pop(context, 'Cancel'),
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => {dao.remove(session), Navigator.pop(context, 'OK')},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
156
lib/widgets/generic/elements/form_search_input.dart
Normal file
156
lib/widgets/generic/elements/form_search_input.dart
Normal file
@ -0,0 +1,156 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||
|
||||
class FormSearchInput extends StatefulWidget {
|
||||
const FormSearchInput({super.key, required this.sessionController});
|
||||
|
||||
final TextEditingController sessionController;
|
||||
|
||||
@override
|
||||
State<FormSearchInput> createState() => _FormSearchInputState();
|
||||
}
|
||||
|
||||
class _FormSearchInputState extends State<FormSearchInput> {
|
||||
String? _currentQuery;
|
||||
|
||||
// The most recent suggestions received from the API.
|
||||
late Iterable<Widget> _lastOptions = <Widget>[];
|
||||
|
||||
late final _Debounceable<Iterable<String>?, String> _debouncedSearch;
|
||||
|
||||
// Calls the "remote" API to search with the given query. Returns null when
|
||||
// the call has been made obsolete.
|
||||
Future<Iterable<String>?> _search(String query) async {
|
||||
_currentQuery = query;
|
||||
|
||||
// In a real application, there should be some error handling here.
|
||||
final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
|
||||
|
||||
// If another search happened after this one, throw away these options.
|
||||
if (_currentQuery != query) {
|
||||
return null;
|
||||
}
|
||||
_currentQuery = null;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_debouncedSearch = _debounce<Iterable<String>?, String>(_search);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SearchAnchor(
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return FormTextInput(
|
||||
controller: widget.sessionController,
|
||||
title: 'Location (optional)',
|
||||
icon: Icon(Icons.search_rounded),
|
||||
maxLines: 2,
|
||||
onTap: () {
|
||||
controller.openView();
|
||||
});
|
||||
}, suggestionsBuilder:
|
||||
(BuildContext context, SearchController controller) async {
|
||||
final List<String>? options =
|
||||
(await _debouncedSearch(controller.text))?.toList();
|
||||
if (options == null) {
|
||||
return _lastOptions;
|
||||
}
|
||||
_lastOptions = List<ListTile>.generate(options.length, (int index) {
|
||||
final String item = options[index];
|
||||
return ListTile(
|
||||
title: Text(item),
|
||||
onTap: () {
|
||||
if (item.isNotEmpty) {
|
||||
widget.sessionController.text = item;
|
||||
}
|
||||
controller.closeView(item);
|
||||
// debugPrint('You just selected $item');
|
||||
// Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return _lastOptions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const Duration fakeAPIDuration = Duration(seconds: 1);
|
||||
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);
|
||||
|
||||
/// Returns a new function that is a debounced version of the given function.
|
||||
///
|
||||
/// This means that the original function will be called only after no calls
|
||||
/// have been made for the given Duration.
|
||||
_Debounceable<S, T> _debounce<S, T>(_Debounceable<S?, T> function) {
|
||||
_DebounceTimer? debounceTimer;
|
||||
|
||||
return (T parameter) async {
|
||||
if (debounceTimer != null && !debounceTimer!.isCompleted) {
|
||||
debounceTimer!.cancel();
|
||||
}
|
||||
debounceTimer = _DebounceTimer();
|
||||
try {
|
||||
await debounceTimer!.future;
|
||||
} on _CancelException {
|
||||
return null;
|
||||
}
|
||||
return function(parameter);
|
||||
};
|
||||
}
|
||||
|
||||
// A wrapper around Timer used for debouncing.
|
||||
class _DebounceTimer {
|
||||
_DebounceTimer() {
|
||||
_timer = Timer(debounceDuration, _onComplete);
|
||||
}
|
||||
|
||||
late final Timer _timer;
|
||||
final Completer<void> _completer = Completer<void>();
|
||||
|
||||
void _onComplete() {
|
||||
_completer.complete();
|
||||
}
|
||||
|
||||
Future<void> get future => _completer.future;
|
||||
|
||||
bool get isCompleted => _completer.isCompleted;
|
||||
|
||||
void cancel() {
|
||||
_timer.cancel();
|
||||
_completer.completeError(const _CancelException());
|
||||
}
|
||||
}
|
||||
|
||||
// An exception indicating that the timer was canceled.
|
||||
class _CancelException implements Exception {
|
||||
const _CancelException();
|
||||
}
|
52
lib/widgets/generic/elements/form_text_input.dart
Normal file
52
lib/widgets/generic/elements/form_text_input.dart
Normal file
@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FormTextInput extends StatelessWidget {
|
||||
const FormTextInput(
|
||||
{super.key,
|
||||
required this.controller,
|
||||
required this.title,
|
||||
this.icon,
|
||||
this.maxLines,
|
||||
this.minLines,
|
||||
this.onTap});
|
||||
|
||||
final TextEditingController controller;
|
||||
final String title;
|
||||
final int? maxLines;
|
||||
final int? minLines;
|
||||
final Icon? icon;
|
||||
final dynamic onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: TextFormField(
|
||||
minLines: minLines ?? 1,
|
||||
maxLines: maxLines ?? 1,
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
prefixIcon: icon ?? Icon(Icons.draw_rounded),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
labelText: title,
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter some text';
|
||||
}
|
||||
|
||||
if (value.length < 3) {
|
||||
return 'Please enter a minimum of 3 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ import 'package:sendtrain/classes/activity_action.dart';
|
||||
import 'package:sendtrain/database/database.dart' hide ActivityAction;
|
||||
import 'package:sendtrain/models/activity_model.dart';
|
||||
|
||||
import '../widgets/activities_header.dart';
|
||||
import '../widgets/activity_card.dart';
|
||||
import '../widgets/activities/activities_header.dart';
|
||||
import '../widgets/activities/activity_card.dart';
|
||||
|
||||
class ActivitiesScreen extends StatefulWidget {
|
||||
const ActivitiesScreen({super.key});
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import '../widgets/session_card.dart';
|
||||
import '../widgets/sessions/session_card.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class SessionsScreen extends StatefulWidget {
|
@ -1,187 +0,0 @@
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/widgets/session_view.dart';
|
||||
|
||||
class SessionCreator extends StatefulWidget {
|
||||
const SessionCreator({super.key, this.data, this.session});
|
||||
|
||||
final Session? session;
|
||||
final Map<String, dynamic>? data;
|
||||
|
||||
@override
|
||||
State<SessionCreator> createState() => _SessionCreatorState();
|
||||
}
|
||||
|
||||
class _SessionCreatorState extends State<SessionCreator> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
final Map<String, TextEditingController> sessionCreateController = {
|
||||
'name': TextEditingController(),
|
||||
'content': TextEditingController(),
|
||||
'status': TextEditingController(),
|
||||
'date': TextEditingController(),
|
||||
};
|
||||
|
||||
Future createSession(context) {
|
||||
return SessionsDao(Provider.of<AppDatabase>(context, listen: false))
|
||||
.createOrUpdate(SessionsCompanion(
|
||||
title: Value(sessionCreateController['name']!.text),
|
||||
content: Value(sessionCreateController['content']!.text),
|
||||
status: Value(SessionStatus.pending),
|
||||
date:
|
||||
Value(DateTime.parse(sessionCreateController['date']!.text))));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
sessionCreateController['date']!.text =
|
||||
DateFormat('yyyy-MM-dd').format(DateTime.now());
|
||||
|
||||
return SimpleDialog(
|
||||
title: Text('Create Session', textAlign: TextAlign.center),
|
||||
titlePadding: EdgeInsets.only(top: 17.5, bottom: 10),
|
||||
contentPadding: EdgeInsets.only(left: 30, right: 30, bottom: 15),
|
||||
children: [
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: TextFormField(
|
||||
controller: sessionCreateController['name'],
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Enter session name',
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter some text';
|
||||
}
|
||||
|
||||
if (value.length <= 3 || value.length > 42) {
|
||||
return 'Please enter between 3 and 42 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: TextFormField(
|
||||
controller: sessionCreateController['content'],
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Enter session description',
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter some text';
|
||||
}
|
||||
|
||||
if (value.length <= 3 || value.length > 256) {
|
||||
return 'Please enter between 3 and 256 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 7.5, bottom: 7.5),
|
||||
child: TextFormField(
|
||||
readOnly: true,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Enter a date for the session',
|
||||
),
|
||||
controller: sessionCreateController['date'],
|
||||
onTap: () {
|
||||
showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime.now()
|
||||
.subtract(Duration(days: 365)),
|
||||
lastDate:
|
||||
DateTime.now().add(Duration(days: 365)))
|
||||
.then((date) {
|
||||
sessionCreateController['date']?.text =
|
||||
DateFormat('yyyy-MM-dd').format(date!);
|
||||
});
|
||||
})),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => {
|
||||
if (_formKey.currentState!.validate())
|
||||
{
|
||||
createSession(_formKey.currentContext)
|
||||
.then((id) => {
|
||||
SessionsDao(
|
||||
Provider.of<AppDatabase>(
|
||||
context,
|
||||
listen: false))
|
||||
.find(id)
|
||||
.then(
|
||||
(session) =>
|
||||
showGeneralDialog(
|
||||
barrierColor: Colors
|
||||
.black
|
||||
.withOpacity(0.5),
|
||||
transitionDuration:
|
||||
const Duration(
|
||||
milliseconds:
|
||||
220),
|
||||
transitionBuilder:
|
||||
(BuildContext
|
||||
context,
|
||||
Animation<
|
||||
double>
|
||||
animation,
|
||||
Animation<
|
||||
double>
|
||||
secondaryAnimation,
|
||||
Widget
|
||||
child) {
|
||||
Animation<Offset> custom = Tween<
|
||||
Offset>(
|
||||
begin:
|
||||
const Offset(
|
||||
0.0,
|
||||
1.0),
|
||||
end: const Offset(
|
||||
0.0,
|
||||
0.0))
|
||||
.animate(
|
||||
animation);
|
||||
return SlideTransition(
|
||||
position:
|
||||
custom,
|
||||
child: Dialog.fullscreen(
|
||||
child: SessionView(
|
||||
session:
|
||||
session)));
|
||||
},
|
||||
barrierDismissible:
|
||||
true,
|
||||
barrierLabel: '',
|
||||
context: context,
|
||||
pageBuilder: (context,
|
||||
animation1,
|
||||
animation2) {
|
||||
return Container();
|
||||
}).whenComplete(() => Navigator.pop(context, 'Submit')),
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
child: Text('Submit'))
|
||||
])
|
||||
],
|
||||
))
|
||||
]);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import 'package:sendtrain/daos/media_items_dao.dart';
|
||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart' hide ActivityAction;
|
||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||
import 'package:sendtrain/widgets/session_view.dart';
|
||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||
|
||||
class SessionCard extends StatefulWidget {
|
||||
final int type;
|
||||
@ -44,28 +44,7 @@ class _SessionCardState extends State<SessionCard> {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
splashColor: Colors.deepPurple,
|
||||
onTap: () => showGeneralDialog(
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
transitionDuration: const Duration(milliseconds: 220),
|
||||
transitionBuilder: (BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child) {
|
||||
Animation<Offset> custom = Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
end: const Offset(0.0, 0.0))
|
||||
.animate(animation);
|
||||
return SlideTransition(
|
||||
position: custom,
|
||||
child: Dialog.fullscreen(
|
||||
child: SessionView(session: session)));
|
||||
},
|
||||
barrierDismissible: true,
|
||||
barrierLabel: '',
|
||||
context: context,
|
||||
pageBuilder: (context, animation1, animation2) {
|
||||
return Container();
|
||||
}),
|
||||
onTap: () => showSessionDialog(session, context),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
@ -94,35 +73,17 @@ class _SessionCardState extends State<SessionCard> {
|
||||
alignment: Alignment.topCenter,
|
||||
icon: Icon(Icons.close_rounded),
|
||||
onPressed: () {
|
||||
showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: const Text('Session Removal'),
|
||||
content: const Text(
|
||||
'Would you like to permanently remove this session?'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => {
|
||||
Navigator.pop(context, 'Cancel'),
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => {
|
||||
showRemovalDialog(
|
||||
'Session Removal',
|
||||
'Would you like to permanently remove this session?',
|
||||
context,
|
||||
SessionsDao(Provider.of<AppDatabase>(
|
||||
context,
|
||||
listen: false))
|
||||
.remove(session)
|
||||
listen: false)),
|
||||
session)
|
||||
.then((result) {
|
||||
setState(() {});
|
||||
}),
|
||||
Navigator.pop(context, 'OK')
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -147,28 +108,7 @@ class _SessionCardState extends State<SessionCard> {
|
||||
splashColor: Colors.deepPurple,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.elliptical(10, 10)),
|
||||
onTap: () => showGeneralDialog(
|
||||
// barrierColor: Colors.black.withOpacity(0.5),
|
||||
transitionDuration: const Duration(milliseconds: 220),
|
||||
transitionBuilder: (BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child) {
|
||||
Animation<Offset> custom = Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
end: const Offset(0.0, 0.0))
|
||||
.animate(animation);
|
||||
return SlideTransition(
|
||||
position: custom,
|
||||
child: Dialog.fullscreen(
|
||||
child: SessionView(session: session)));
|
||||
},
|
||||
barrierDismissible: true,
|
||||
barrierLabel: '',
|
||||
context: context,
|
||||
pageBuilder: (context, animation1, animation2) {
|
||||
return Container();
|
||||
}),
|
||||
onTap: () => showSessionDialog(session, context),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
// color: const Color.fromARGB(47, 0, 0, 0),
|
141
lib/widgets/sessions/session_creator.dart
Normal file
141
lib/widgets/sessions/session_creator.dart
Normal file
@ -0,0 +1,141 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||
|
||||
class SessionCreator extends StatefulWidget {
|
||||
const SessionCreator({super.key, this.data, this.session});
|
||||
|
||||
final Session? session;
|
||||
final Map<String, dynamic>? data;
|
||||
|
||||
@override
|
||||
State<SessionCreator> createState() => _SessionCreatorState();
|
||||
}
|
||||
|
||||
class _SessionCreatorState extends State<SessionCreator> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
final Map<String, TextEditingController> sessionCreateController = {
|
||||
'name': TextEditingController(),
|
||||
'content': TextEditingController(),
|
||||
'status': TextEditingController(),
|
||||
'date': TextEditingController(),
|
||||
'address': TextEditingController(),
|
||||
'media': TextEditingController(),
|
||||
};
|
||||
|
||||
Future createSession(context) {
|
||||
return SessionsDao(Provider.of<AppDatabase>(context, listen: false))
|
||||
.createOrUpdate(SessionsCompanion(
|
||||
title: Value(sessionCreateController['name']!.text),
|
||||
content: Value(sessionCreateController['content']!.text),
|
||||
status: Value(SessionStatus.pending),
|
||||
date: Value(DateTime.parse(sessionCreateController['date']!.text)),
|
||||
address: Value(sessionCreateController['address']!.text)));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
sessionCreateController['date']!.text =
|
||||
DateFormat('yyyy-MM-dd').format(DateTime.now());
|
||||
|
||||
AppDatabase db = Provider.of<AppDatabase>(context, listen: false);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: Text('Create Session',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge)),
|
||||
FormTextInput(
|
||||
controller: sessionCreateController['name']!,
|
||||
title: 'Title'),
|
||||
FormTextInput(
|
||||
controller: sessionCreateController['content']!,
|
||||
title: 'Description',
|
||||
icon: Icon(Icons.description_rounded),
|
||||
maxLines: 10),
|
||||
FormTextInput(
|
||||
controller: sessionCreateController['date']!,
|
||||
title: 'Date',
|
||||
icon: Icon(Icons.date_range_rounded),
|
||||
onTap: () {
|
||||
showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime.now().add(Duration(days: 365)))
|
||||
.then((date) {
|
||||
if (date != null) {
|
||||
sessionCreateController['date']?.text =
|
||||
DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
});
|
||||
}),
|
||||
// Padding(
|
||||
// padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
// child: TextFormField(
|
||||
// readOnly: true,
|
||||
// decoration: InputDecoration(
|
||||
// prefixIcon: Icon(Icons.image_rounded),
|
||||
// filled: true,
|
||||
// border: OutlineInputBorder(
|
||||
// borderSide: BorderSide.none,
|
||||
// borderRadius: BorderRadius.circular(12),
|
||||
// ),
|
||||
// labelText: 'Select Media (optional)',
|
||||
// ),
|
||||
// controller: sessionCreateController['media'],
|
||||
// onTap: () async {
|
||||
// FilePickerResult? result = await FilePicker.platform
|
||||
// .pickFiles(allowMultiple: true);
|
||||
|
||||
// if (result != null) {
|
||||
// List<File> files = result.paths
|
||||
// .map((path) => File(path!))
|
||||
// .toList();
|
||||
// }
|
||||
// })),
|
||||
FormSearchInput(
|
||||
sessionController: sessionCreateController['address']!),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: FilledButton(
|
||||
onPressed: () => {
|
||||
if (_formKey.currentState!.validate())
|
||||
{
|
||||
createSession(_formKey.currentContext)
|
||||
.then((id) => {
|
||||
SessionsDao(db).find(id).then(
|
||||
(session) => showSessionDialog(
|
||||
session,
|
||||
_formKey
|
||||
.currentContext!)),
|
||||
Navigator.pop(
|
||||
_formKey.currentContext!,
|
||||
'Submit')
|
||||
})
|
||||
}
|
||||
},
|
||||
child: Text('Submit')))
|
||||
]),
|
||||
],
|
||||
)));
|
||||
}
|
||||
}
|
@ -7,9 +7,9 @@ import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/daos/activities_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||
import 'package:sendtrain/widgets/session_view_achievements.dart';
|
||||
import 'package:sendtrain/widgets/session_view_activities.dart';
|
||||
import 'package:sendtrain/widgets/session_view_media.dart';
|
||||
import 'package:sendtrain/widgets/sessions/session_view_achievements.dart';
|
||||
import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
|
||||
import 'package:sendtrain/widgets/sessions/session_view_media.dart';
|
||||
|
||||
class SessionView extends StatefulWidget {
|
||||
const SessionView({super.key, required this.session});
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/widgets/activity_card.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_card.dart';
|
||||
|
||||
class SessionViewActivities extends StatelessWidget {
|
||||
const SessionViewActivities({super.key, required this.activities });
|
@ -44,6 +44,8 @@ dependencies:
|
||||
drift: ^2.22.1
|
||||
flutter_expandable_fab: ^2.3.0
|
||||
drift_flutter: ^0.2.2
|
||||
map_location_picker: ^1.2.7
|
||||
file_picker: ^8.1.7
|
||||
|
||||
flutter_launcher_name:
|
||||
name: "SendTrain"
|
||||
|
Loading…
x
Reference in New Issue
Block a user