Skip to content

Commit

Permalink
Add subtitles to movie details
Browse files Browse the repository at this point in the history
Add subtitle model
Add service to fetch the subtitles crawled from the DOM
Known issue: #15
  • Loading branch information
devrnt committed Sep 22, 2018
1 parent c82ee71 commit 03b1e04
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 11 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
Expand Down
9 changes: 9 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ subprojects {
}
subprojects {
project.evaluationDependsOn(':app')
// this is fix for the compile and runtime version error
project.configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.android.support'
&& !details.requested.name.contains('multidex') ) {
details.useVersion "26.0.1"
}
}
}
}

task clean(type: Delete) {
Expand Down
Binary file added assets/images/flag_placeholder.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions lib/models/subtitle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
class Subtitle {
String language;
// String name;
String imageUrl;
String downloadUrl;
String countryCode;
int rating;
static final String baseUrl = 'https://www.yifysubtitles.com';

// Fill this map up for every country name that is not
// directly convertable for a flag code on the site
// https://countryflags.io/

final Map<String, String> countryCodes = {
'english': 'GB',
'dutch': 'NL',
'danish': 'DK',
'portuguese': 'PT',
'spanish': 'ES',
'turkish': 'TR',
'polish': 'PL',
'hebrew': 'IL',
'bulgarian': 'BG',
'arabic': 'SA',
'croatian': 'HR',
'chinese': 'CN',
'serbian': 'RS',
'bengali': 'BD',
'swedish': 'SE',
'korean': 'KR',
'malay': 'MY',
'farsi/persian': 'IR'
};

Subtitle({this.language, String downloadUrl, this.rating}) {
// default href is not the download link
// this might change on the subtitle website
String formattedDownloadUrl =
downloadUrl.replaceFirst('/subtitles', '/subtitle');

this.downloadUrl = '$baseUrl$formattedDownloadUrl.zip';
this.countryCode = _getCountryCode(language);
}

String _getCountryCode(String language) {
if (countryCodes.containsKey(language.toLowerCase())) {
return countryCodes[language.toLowerCase()];
} else {
// suppose country code is first to chars of language
// this works most of the time
return language.substring(0, 2);
}
}
}
4 changes: 2 additions & 2 deletions lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class _HomeScreenState extends State<HomeScreen>
}

TabBarView _buildTabBarView() {
_fetchLatestMovies();
// _fetchLatestMovies();
return TabBarView(
controller: _tabController,
children: <Widget>[
Expand Down Expand Up @@ -279,7 +279,7 @@ class _HomeScreenState extends State<HomeScreen>
)
: Center(child: CircularProgressIndicator());
},
)
),
],
);
}
Expand Down
167 changes: 164 additions & 3 deletions lib/screens/movie_details_screen.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:movie_catalog/models/subtitle.dart';
import 'package:movie_catalog/services/storage_service.dart';
import 'package:movie_catalog/services/subtitle_service.dart';

import 'package:url_launcher/url_launcher.dart';

import 'package:movie_catalog/models/movie.dart';
import 'package:movie_catalog/models/torrent.dart';

import 'package:simple_permissions/simple_permissions.dart';

class MovieDetails extends StatefulWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

final Movie movie;

MovieDetails({this.movie});
Expand Down Expand Up @@ -38,7 +43,7 @@ class MovieDetails extends StatefulWidget {
child: FadeInImage.assetNetwork(
fadeInDuration: Duration(milliseconds: 100),
fadeInCurve: Curves.linear,
image: movie.coverImageLarge??movie.coverImageMedium,
image: movie.coverImageLarge ?? movie.coverImageMedium,
placeholder: 'assets/images/cover_placeholder.jpg',
fit: BoxFit.cover,
height: 170.0,
Expand Down Expand Up @@ -109,7 +114,7 @@ class MovieDetails extends StatefulWidget {
});
formattedGenres = genres.substring(0, genres.length - 2);
} else {
formattedGenres = '';
formattedGenres = 'No genres';
}

// remove the last comma
Expand Down Expand Up @@ -334,7 +339,17 @@ class MovieDetails extends StatefulWidget {

class MovieDetailsState extends State<MovieDetails> {
StorageService _storageService;
SubtitleService _subtitleService;
Subtitle _selectedSubtitle;
Future<List<Subtitle>> _subtitles;

// permissions
String _platformVersion = 'Unknown';
Permission permission = Permission.WriteExternalStorage;

bool liked = false;

bool _subtitlesAvailable = true;
@override
Widget build(BuildContext context) {
return Scaffold(
Expand All @@ -343,6 +358,7 @@ class MovieDetailsState extends State<MovieDetails> {
title: Text(widget.movie.title, style: TextStyle(fontSize: 17.0)),
actions: <Widget>[
IconButton(
tooltip: 'Add movie to your library',
iconSize: 20.0,
icon: Icon(
liked ? Icons.favorite : Icons.favorite_border,
Expand Down Expand Up @@ -397,13 +413,14 @@ class MovieDetailsState extends State<MovieDetails> {
),
widget._buildSummary(),
widget._buildGenres(),
_buildSubtitles(),
Container(
margin: EdgeInsets.all(15.0),
child: Row(
children: widget._buildTorrents(widget.movie.torrents,
Theme.of(context).accentColor, context),
),
)
),
],
),
);
Expand All @@ -413,11 +430,14 @@ class MovieDetailsState extends State<MovieDetails> {
void initState() {
super.initState();
_storageService = new StorageService();
_subtitleService = new SubtitleService();
_subtitles = _subtitleService.getSubtitles(widget.movie.imdbCode);
_storageService.liked(widget.movie).then((result) {
setState(() {
liked = result;
});
});
initPlatformState();
}

void _showSnackBar({String title, Color color, IconData icon}) {
Expand All @@ -443,4 +463,145 @@ class MovieDetailsState extends State<MovieDetails> {
);
widget._scaffoldKey.currentState.showSnackBar(snackbar);
}

Widget _buildSubtitles() {
final String subtitleString = 'subtitles:'.toUpperCase();
return Container(
margin: EdgeInsets.all(15.0),
child: Row(
children: <Widget>[
Text(
subtitleString,
style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold),
),
Padding(
padding: EdgeInsets.only(left: 10.0),
),
FutureBuilder(
future: _subtitles,
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
if (snapshot.hasData) {
if (snapshot.data.isEmpty) {
return Text('No subtitles available');
} else {
return _buildSubtitleDropDown(snapshot.data);
}
} else {
return Center(
child: SizedBox(
height: 12.0,
width: 12.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
),
),
);
}
}),
],
),
);
}

Widget _buildSubtitleDropDown(List<Subtitle> subtitles) {
return Row(
children: <Widget>[
DropdownButton(
style: TextStyle(fontSize: 16.0),
value: _selectedSubtitle,
hint: Text('Language'),
isDense: true,
onChanged: (Subtitle subtitle) {
print(subtitle.language);
setState(() {
_selectedSubtitle = subtitle;
});
},
items: subtitles
.map(
(sub) => DropdownMenuItem(
value: sub,
child: Row(
children: <Widget>[
FadeInImage.assetNetwork(
image:
'https://www.countryflags.io/${sub.countryCode}/flat/32.png',
height: 22.0,
placeholder: 'assets/images/flag_placeholder.jpg',
),
Padding(
padding: EdgeInsets.only(left: 12.0),
),
Text(sub.language),
],
),
),
)
.toList(),
),
_selectedSubtitle != null
? Container(
alignment: Alignment.center,
child: IconButton(
icon: Icon(
Icons.save_alt,
color: Theme.of(context).accentColor,
),
onPressed: () {
_downloadFile(_selectedSubtitle.downloadUrl);
},
),
)
: Container(),
],
);
}

void _downloadFile(String url) async {
if (await checkPermission()) {
await _subtitleService.downloadSubtitle(url);
_showSnackBar(
color: Theme.of(context).accentColor,
icon: Icons.done,
title: 'Subtitle downloaded in your Downloads folder');
} else {
await requestPermission();
if (await checkPermission() == false) {
_showSnackBar(
color: Colors.amber[700],
icon: Icons.warning,
title: 'Please accept the permission');
//
} else {
_downloadFile(url);
}
}
}

// permissions
void initPlatformState() async {
String platformVersion;
try {
platformVersion = await SimplePermissions.platformVersion;
} on Exception {
platformVersion = 'Failed to get platform version';
}

// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;

_platformVersion = platformVersion;
}

requestPermission() async {
final res = await SimplePermissions.requestPermission(permission);
}

Future<bool> checkPermission() async {
bool res = await SimplePermissions.checkPermission(permission);
return res;
}
}
6 changes: 5 additions & 1 deletion lib/services/storage_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class StorageService {

Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();

return directory.path;
}

Expand Down Expand Up @@ -55,4 +54,9 @@ class StorageService {
movies.any((movieFromStorage) => movieFromStorage.id == movie.id);
return flag;
}

Future<String> get downloadsPath async {
final directoryTemp = await getExternalStorageDirectory();
return '${directoryTemp.path}/Download';
}
}
Loading

0 comments on commit 03b1e04

Please sign in to comment.