Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

225 Add course name on downloads card UI #246

Merged
merged 11 commits into from
Jan 29, 2024
31 changes: 29 additions & 2 deletions lib/models/download/download_state_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,44 @@ import 'package:flutter/material.dart';

@immutable
class DownloadState {
final Map<int, String> downloadedVideos;
final Map<int, VideoDetails> downloadedVideos;

const DownloadState({
this.downloadedVideos = const {},
});

DownloadState copyWith({
Map<int, String>? downloadedVideos,
Map<int, VideoDetails>? downloadedVideos,
}) {
return DownloadState(
downloadedVideos: downloadedVideos ?? this.downloadedVideos,
);
}
}

@immutable
class VideoDetails {
final String filePath;
final String name;
final int duration; // Duration in seconds or your preferred unit

const VideoDetails({
required this.filePath,
required this.name,
required this.duration,
});

VideoDetails copyWith({
String? filePath,
String? name,
int? duration,
}) {
return VideoDetails(
filePath: filePath ?? this.filePath,
name: name ?? this.name,
duration: duration ?? this.duration,
);
}

// Implement toJson and fromJson if you need to serialize/deserialize the object
}
101 changes: 68 additions & 33 deletions lib/view_models/download_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,40 @@ import 'package:logger/logger.dart';
import 'dart:io';

class DownloadViewModel extends StateNotifier<DownloadState> {

final Logger _logger = Logger();

DownloadViewModel() : super(const DownloadState()) {
initDownloads();
}

bool isStreamDownloaded(int id) {
final int streamIdInt = id.toInt(); // Convert Int64 to int
return state.downloadedVideos.containsKey(streamIdInt);
}


Future<void> initDownloads() async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString('downloadedVideos');
if (jsonString != null) {
final downloaded = Map<String, dynamic>.from(json.decode(jsonString));
final downloadedVideos = downloaded
.map((key, value) => MapEntry(int.parse(key), value.toString()));
final downloadedVideos = downloaded.map((key, value) {
// Decode the JSON string into a Map
final videoDetailsMap = json.decode(value);
// Create a VideoDetails object from the Map
final videoDetails = VideoDetails(
filePath: videoDetailsMap['filePath'],
name: videoDetailsMap['name'],
duration: videoDetailsMap['duration'],
);
return MapEntry(int.parse(key), videoDetails);
}).cast<int, VideoDetails>(); // Ensure the map has the correct type
state = state.copyWith(downloadedVideos: downloadedVideos);
}
}



Future<String> downloadVideo(
String videoUrl, int streamId, String fileName,) async {
Future<String> downloadVideo(String videoUrl, int streamId, String fileName,
String streamName, int streamDuration,) async {
try {
final directory = await getApplicationDocumentsDirectory();
final filePath = '${directory.path}/$fileName';
Expand All @@ -39,14 +51,39 @@ class DownloadViewModel extends StateNotifier<DownloadState> {

final prefs = await SharedPreferences.getInstance();
final int streamIdInt = streamId.toInt();
final downloadedVideos = Map<int, String>.from(state.downloadedVideos)
..[streamIdInt] = filePath;

// Save to SharedPreferences
// Create a map for the video details
final videoDetailsMap = {
'filePath': filePath,
'name': streamName,
'duration': streamDuration,
};

// Convert video details map to JSON string
final videoDetailsJson = json.encode(videoDetailsMap);

// Save the JSON string in your SharedPreferences
final downloadedVideosJson = Map<int, String>.from(state.downloadedVideos)
..[streamIdInt] = videoDetailsJson;

await prefs.setString(
'downloadedVideos',
json.encode(downloadedVideos
.map((key, value) => MapEntry(key.toString(), value)),),);
'downloadedVideos',
json.encode(downloadedVideosJson.map((key, value) =>
MapEntry(key.toString(), value),),),
);

// Convert the JSON strings back to VideoDetails objects for the state
final downloadedVideos = downloadedVideosJson.map((key, value) {
final videoDetailsMap = json.decode(value);
final videoDetails = VideoDetails(
filePath: videoDetailsMap['filePath'],
name: videoDetailsMap['name'],
duration: videoDetailsMap['duration'],
);
return MapEntry(key, videoDetails);
}).cast<int, VideoDetails>();

// Update the state
state = state.copyWith(downloadedVideos: downloadedVideos);
_logger.d('Downloaded videos: ${state.downloadedVideos}');
return filePath;
Expand All @@ -63,15 +100,15 @@ class DownloadViewModel extends StateNotifier<DownloadState> {
_logger.e('Error fetching downloaded videos: $e');
}
}

Future<void> deleteDownload(int videoId) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a small adjustment, before deleting a downloaded video, we can show the user a dialogue card like "Are you sure you want to delete?" or similar. because I can delete the video directly and it is not very optimal in terms of user experience

_logger.i('Deleting downloaded video with ID: $videoId');
_logger.d('Current state before deletion: ${state.downloadedVideos}');

try {
String? filePath = state.downloadedVideos[videoId];
_logger.d('File path to delete: $filePath');

if (filePath != null && filePath.isNotEmpty) {
// Get the VideoDetails object from the state
VideoDetails? videoDetails = state.downloadedVideos[videoId];
if (videoDetails != null) {
final filePath = videoDetails.filePath;
final file = File(filePath);
if (await file.exists()) {
await file.delete();
Expand All @@ -80,23 +117,23 @@ class DownloadViewModel extends StateNotifier<DownloadState> {
_logger.w('File not found: $filePath');
}

// Update the state and SharedPreferences after deletion
final updatedDownloads = Map<int, String>.from(state.downloadedVideos);
final prefs = await SharedPreferences.getInstance();
final updatedDownloads = Map<int, VideoDetails>.from(
state.downloadedVideos,);
updatedDownloads.remove(videoId);

final prefs = await SharedPreferences.getInstance();
// Save updated list to SharedPreferences
// Convert VideoDetails objects to JSON strings before saving
await prefs.setString(
'downloadedVideos',
json.encode(updatedDownloads.map((key, value) => MapEntry(key.toString(), value))),
);

json.encode(updatedDownloads.map((key, value) =>
MapEntry(key.toString(), json.encode(value)),),)
,);
state = state.copyWith(downloadedVideos: updatedDownloads);
_logger.d('Updated state after deletion: ${state.downloadedVideos}');
} else {
_logger.w('No file path found for video ID: $videoId');
}
} catch (e) {
_logger.e('Error deleting video with ID $videoId: $e');
}
catch (e) {
_logger.e('Error deleting video with ID $videoId: $e');
}
}

Expand Down Expand Up @@ -126,8 +163,6 @@ class DownloadViewModel extends StateNotifier<DownloadState> {
}
}

bool isStreamDownloaded(int id) {
final int streamIdInt = id.toInt(); // Convert Int64 to int
return state.downloadedVideos.containsKey(streamIdInt);
}

}

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:gocast_mobile/models/download/download_state_model.dart';
import 'package:gocast_mobile/providers.dart';
import 'package:gocast_mobile/views/components/custom_search_top_nav_bar.dart';
import 'package:gocast_mobile/views/course_view/downloaded_courses_view/download_card.dart';
Expand Down Expand Up @@ -42,14 +42,17 @@ class DownloadedCoursesState extends ConsumerState<DownloadedCourses> {
),
videoCards: downloadedVideos.entries.map((entry) {
final int videoId = entry.key;
final String localPath = entry.value;

final VideoDetails videoDetails = entry.value;
final String localPath = videoDetails.filePath;
final String videoName = videoDetails.name;
final int durationSeconds = videoDetails.duration;
final String formattedDuration = "${(durationSeconds ~/ 3600).toString().padLeft(2, '0')}:${((durationSeconds % 3600) ~/ 60).toString().padLeft(2, '0')}:${(durationSeconds % 60).toString().padLeft(2, '0')}";
return VideoCard(
duration:
"${Random().nextInt(2).toString().padLeft(2, '0')}:${Random().nextInt(59).toString().padLeft(2, '0')}:${Random().nextInt(60).toString().padLeft(2, '0')}",
formattedDuration,
imageName: 'assets/images/course1.png',
// Update as necessary
title: 'Video $videoId',
title: videoName,
// Replace with the appropriate title
date: 'Video Date',
// Replace with the appropriate date
Expand Down
2 changes: 1 addition & 1 deletion lib/views/video_view/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class VideoPlayerPageState extends ConsumerState<VideoPlayerPage> {

ref
.read(downloadViewModelProvider.notifier)
.downloadVideo(downloadUrl, stream.id, fileName,)
.downloadVideo(downloadUrl, stream.id, fileName,stream.name,stream.duration)
.then((localPath) {
if (localPath.isNotEmpty) {
// Download successful
Expand Down
Loading