Skip to content

Commit

Permalink
ingredient form submit
Browse files Browse the repository at this point in the history
  • Loading branch information
riverscuomo committed Jul 22, 2024
1 parent a9c82b4 commit 8131668
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 91 deletions.
3 changes: 2 additions & 1 deletion lib/screens/update_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ class _UpdateScreenState extends State<UpdateScreen> {
onChanged: (value) =>
updateJob(index, job.copyWith(playlistId: value)),
),
IngredientManagementWidget(
const SizedBox(height: 20),
IngredientForm(
initialIngredients: job.recipe,
onIngredientsChanged: (updatedIngredients) {
setState(() {
Expand Down
64 changes: 41 additions & 23 deletions lib/services/spotify_service.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:spotify/spotify.dart';
import 'dart:convert';
import 'dart:html' as html;
import 'package:spotify/spotify.dart';

class SpotifyService {
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage();
static const String _accessTokenKey = 'accessToken';
static const String _refreshTokenKey = 'refreshToken';

late SpotifyApi _spotify;
final String clientId;
Expand Down Expand Up @@ -45,10 +46,11 @@ class SpotifyService {
if (response.statusCode == 200) {
final tokenData = json.decode(response.body);
final accessToken = tokenData['access_token'];
final refreshToken = tokenData['refresh_token'];
await _secureStorage.write(key: _accessTokenKey, value: accessToken);
await _secureStorage.write(key: _refreshTokenKey, value: refreshToken);
print('Access Token: $accessToken');
_spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret,
accessToken: accessToken));
_spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret, accessToken: accessToken));
} else {
print('Failed to exchange code for token: ${response.body}');
throw Exception('Failed to authenticate with Spotify');
Expand All @@ -71,9 +73,8 @@ class SpotifyService {
return await _secureStorage.read(key: _accessTokenKey);
}

// refresh token
Future<void> refreshAccessToken() async {
final refreshToken = await _secureStorage.read(key: 'refreshToken');
final refreshToken = await _secureStorage.read(key: _refreshTokenKey);
final tokenEndpoint = Uri.parse('https://accounts.spotify.com/api/token');
final response = await http.post(
tokenEndpoint,
Expand All @@ -85,46 +86,63 @@ class SpotifyService {
'client_secret': clientSecret,
},
);
print(response);

if (response.statusCode == 200) {
final tokenData = json.decode(response.body);
final accessToken = tokenData['access_token'];
await _secureStorage.write(key: _accessTokenKey, value: accessToken);
print('Access Token: $accessToken');
_spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret,
accessToken: accessToken));
print('Access Token refreshed: $accessToken');
_spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret, accessToken: accessToken));
} else {
print('Failed to refresh token: ${response.body}');
throw Exception('Failed to refresh token');
}
}

Future<List<PlaylistSimple>> getUserPlaylists(String userId) async {
Future<void> _ensureAuthenticated() async {
final accessToken = await getAccessToken();
if (accessToken == null) {
throw Exception('Not authenticated');
}
_spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret,
accessToken: accessToken));
_spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret, accessToken: accessToken));
}

Future<Playlist> getPlaylist(String playlistId) async {
await _ensureAuthenticated();
try {
return await _spotify.playlists.get(playlistId);
} catch (e) {
print('Error fetching playlist: $e');
rethrow;
}
}

Future<List<PlaylistSimple>> getUserPlaylists(String userId) async {
await _ensureAuthenticated();
final playlists = await _spotify.playlists.getUsersPlaylists(userId).all();
return playlists.toList();
}

Future<void> updatePlaylist(String playlistId,
{String? name, String? description}) async {
final accessToken = await getAccessToken();
if (accessToken == null) {
throw Exception('Not authenticated');
}
// _spotify = SpotifyApi(SpotifyApiCredentials(clientId, clientSecret, accessToken: accessToken));
// await _spotify.playlists.updatePlaylist(playlistId, description: description);
Future<void> updatePlaylist(String playlistId, {String? name, String? description}) async {
await _ensureAuthenticated();
// await _spotify.playlists.changePlaylistDetails(playlistId, name: name, description: description);
}

Future<Artist> getArtist(String artistId) async {
await _ensureAuthenticated();
return await _spotify.artists.get(artistId);
}

Future<Iterable<Track>> getPlaylistTracks(String playlistId) async {
await _ensureAuthenticated();
return await _spotify.playlists.getTracksByPlaylistId(playlistId).all();
}

Future<void> logout() async {
await _secureStorage.delete(key: _accessTokenKey);
_initializeSpotify(); // Reset to unauthenticated state
await _secureStorage.delete(key: _refreshTokenKey);
_initializeSpotify();
}

// Add more methods as needed...
}
}
172 changes: 107 additions & 65 deletions lib/widgets/ingredient_management_widget.dart
Original file line number Diff line number Diff line change
@@ -1,108 +1,150 @@
import 'package:flutter/material.dart';
import 'package:spotkin_flutter/app_core.dart';
import 'package:flutter/material.dart';

class IngredientManagementWidget extends StatefulWidget {
class IngredientForm extends StatefulWidget {
final List<Ingredient> initialIngredients;
final Function(List<Ingredient>) onIngredientsChanged;

const IngredientManagementWidget({
const IngredientForm({
Key? key,
required this.initialIngredients,
required this.onIngredientsChanged,
}) : super(key: key);

@override
_IngredientManagementWidgetState createState() => _IngredientManagementWidgetState();
_IngredientFormState createState() => _IngredientFormState();
}

class _IngredientManagementWidgetState extends State<IngredientManagementWidget> {
late List<Ingredient> _ingredients;
class _IngredientFormState extends State<IngredientForm> {
final _formKey = GlobalKey<FormState>();
late List<IngredientFormRow> _ingredientRows;

@override
void initState() {
super.initState();
_ingredients = List.from(widget.initialIngredients);
_initIngredientRows();
}

void _initIngredientRows() {
_ingredientRows = widget.initialIngredients.map((ingredient) =>
IngredientFormRow(
playlistController: TextEditingController(text: ingredient.sourcePlaylistId),
quantityController: TextEditingController(text: ingredient.quantity.toString()),
)
).toList();
if (_ingredientRows.isEmpty) {
_addNewRow();
}
}

@override
void didUpdateWidget(IngredientManagementWidget oldWidget) {
void didUpdateWidget(IngredientForm oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialIngredients != oldWidget.initialIngredients) {
setState(() {
_ingredients = List.from(widget.initialIngredients);
});
_initIngredientRows();
}
}

void _updateIngredient(int index, Ingredient updatedIngredient) {
void _addNewRow() {
setState(() {
_ingredients[index] = updatedIngredient;
widget.onIngredientsChanged(_ingredients);
_ingredientRows.add(IngredientFormRow(
playlistController: TextEditingController(),
quantityController: TextEditingController(),
));
});
}

void _removeIngredient(int index) {
setState(() {
_ingredients.removeAt(index);
widget.onIngredientsChanged(_ingredients);
_ingredientRows.removeAt(index);
});
}

void _submitAndAddIngredient() {
if (_formKey.currentState!.validate()) {
List<Ingredient> updatedIngredients = _ingredientRows.map((row) =>
Ingredient(
sourcePlaylistName: '', // You might want to update this if needed
sourcePlaylistId: row.playlistController.text,
quantity: int.tryParse(row.quantityController.text) ?? 0,
)
).toList();

widget.onIngredientsChanged(updatedIngredients);
_addNewRow();
}
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Ingredients', style: Theme.of(context).textTheme.titleMedium),
..._ingredients.asMap().entries.map((entry) {
int idx = entry.key;
Ingredient ingredient = entry.value;
return Row(
children: [
Expanded(
child: TextFormField(
key: ValueKey('sourcePlaylistId_$idx'),
initialValue: ingredient.sourcePlaylistId,
decoration: const InputDecoration(
labelText: 'Source playlist link',
hintText: 'Enter Spotify playlist link or ID',
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Ingredients', style: Theme.of(context).textTheme.titleMedium),
..._ingredientRows.asMap().entries.map((entry) {
int idx = entry.key;
IngredientFormRow row = entry.value;
return Row(
children: [
Expanded(
child: TextFormField(
controller: row.playlistController,
decoration: const InputDecoration(
labelText: 'Source playlist link',
hintText: 'Enter Spotify playlist link or ID',
),
validator: Utils.validateSpotifyPlaylistInput,
),
validator: Utils.validateSpotifyPlaylistInput,
onChanged: (value) {
_updateIngredient(idx, ingredient.copyWith(sourcePlaylistId: value));
},
),
),
const SizedBox(width: 10),
Expanded(
child: TextFormField(
key: ValueKey('quantity_$idx'),
initialValue: ingredient.quantity.toString(),
decoration: const InputDecoration(labelText: 'Quantity'),
keyboardType: TextInputType.number,
onChanged: (value) {
_updateIngredient(idx, ingredient.copyWith(quantity: int.tryParse(value) ?? 0));
},
const SizedBox(width: 10),
Expanded(
child: TextFormField(
controller: row.quantityController,
decoration: const InputDecoration(labelText: 'Quantity'),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty || int.tryParse(value) == null) {
return 'Please enter a valid number';
}
return null;
},
),
),
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeIngredient(idx),
),

],
);
}),
ElevatedButton(
onPressed: () {
setState(() {
_ingredients.add(Ingredient(sourcePlaylistName: '', sourcePlaylistId: '', quantity: 0));
widget.onIngredientsChanged(_ingredients);
});
},
child: const Text('Add Ingredient'),
),
],
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeIngredient(idx),
),
],
);
}),
ElevatedButton(
onPressed: _submitAndAddIngredient,
child: const Text('Add Ingredient'),
),
],
),
);
}

@override
void dispose() {
for (var row in _ingredientRows) {
row.playlistController.dispose();
row.quantityController.dispose();
}
super.dispose();
}
}

class IngredientFormRow {
final TextEditingController playlistController;
final TextEditingController quantityController;

IngredientFormRow({
required this.playlistController,
required this.quantityController,
});
}
4 changes: 2 additions & 2 deletions lib/widgets/job_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ class _JobFormState extends State<JobForm> {
),
TextFormField(
controller: _playlistIdController,
decoration: const InputDecoration(labelText: 'Target playlist link'),
decoration: const InputDecoration(labelText: 'Target playlist'),
validator: (value) =>
value?.isEmpty ?? true ? 'Please enter a playlist ID' : null,
value?.isEmpty ?? true ? 'Please enter a Spotify playlist link' : null,
),

const SizedBox(height: 10),
Expand Down

0 comments on commit 8131668

Please sign in to comment.