To show book content to fit screen - flutter

I am developing an app which is showing epub content. But I want to make it responsive. I am getting all tags from epub file and adding it to page. And I calculated how much chars can get into screen and controlling will content overflow. But paragraphs has too many character than maximumum char it will adding page overflowing.
This is the code that parsing paragraphs and adding to page :
readBook() async {
ByteData data = await rootBundle.load("assets/dosya4.epub");
List<int> bookBytes =
data.buffer.asInt8List(data.offsetInBytes, data.lengthInBytes);
epubBook = await EpubReader.readBook(bookBytes);
for (var element in epubBook.Content!.Html!.values) {
dom.Document doc = parse(element.Content);
tags.addAll(doc.getElementsByTagName("p"));
}
setState(() {
String page = "";
print(widget.size.height / (2 * fontSize));
maxChar =
((widget.size.width / fontSize) * (widget.size.height / (fontSize)))
.toInt();
print(maxChar);
for (dom.Element tag in tags) {
if (maxChar < page.length + tag.innerHtml.length - 200) {
pages.add(page);
page = tag.outerHtml;
} else {
page += tag.outerHtml;
}
}
});
This is the code how i show pages :
PageView.builder(
controller: pageController,
itemCount: pages.length,
itemBuilder: (context, index) {
pages.removeWhere((element) => element.isEmpty);
return SingleChildScrollView(
child: SelectableHtml(
style: {
"body": Style(
fontSize: FontSize(fontSize),
),
"span.found": Style(
display: Display.INLINE,
backgroundColor:
const Color.fromARGB(255, 255, 255, 0)),
},
data: pages[index],
),
);
},
)
I am thinking if i add to page sentence by sentence it will be good but i thought it will cause bug or is there any package for what i want?

Related

Last item of list view remains even after deleting it in Flutter

I'm building a todo app in which CRUD operations can be carried out. With the help of Sqlflite todo items will be stored/retrieved from the database. When i try to delete todos, it works just as fine except the last item in the list view remains even after deleting it from the database. But, once the app restarts the last item gets removed. Tried setState() manually to refresh the page but nothings works. Help me sort this out and thanks in adavance.
//Holds the todos and will be passed to ListView.builder
List<Todo> listOfValues;
//Holds the indexes of selected items to highlight the items in the ListView
List<int> indexes = [];
//Holds the ids of selected items to perform CRUD
List<int> selectedItems = [];
//This is where the todos are retrieved from the database.
//Following DatabaseModel is a database helper class
DatabaseModel data = DatabaseModel();
data.queryingData().then((value) {
setState(() {
if (value.isNotEmpty) {
listOfValues = value;
}
});
});
Following block builds/returns the body of the main page of the app which is a ListView contains todos.
Widget content() {
return SafeArea(
child: Container(
child: ListView.builder(
itemBuilder: (context, index) {
return Card(
color: (indexes.contains(index))
? Colors.blue.withOpacity(0.5)
: Colors.transparent,
child: ListTile(
title: Text(listOfValues[index].todo),
subtitle: Text('Planned on ${listOfValues[index].date}'),
trailing: Text(listOfValues[index].time),
onLongPress: () {
setState(() {
lonPressEnabled = true;
if (!indexes.contains(index)) {
indexes.add(index);
selectedItems.add(listOfValues[index].id);
}
});
},
onTap: () {
setState(() {
if (indexes.isNotEmpty && lonPressEnabled) {
if (indexes.contains(index)) {
indexes.remove(index);
selectedItems.remove(listOfValues[index].id);
} else {
indexes.add(index);
selectedItems.add(listOfValues[index].id);
}
}
});
},
),
);
},
itemCount: listOfValues.length,
),
),
);}
Following block is used to delete items from database
ElevatedButton(
onPressed: () {
lonPressEnabled = false;
DatabaseModel model = DatabaseModel();
for (int i = 0; i < selectedItems.length; i++) {
model.deletingRecord(selectedItems[i]);
//selectedItems holds the ids of each todos
}
selectedItems = [];
indexes = [];
setState(() {
print();
});
},
child: const Icon(Icons.delete));
Following block deletes todo from the database
deletingRecord(int ids) async {
Database db = await openingDB;
db.rawDelete('delete from ToDos where id=$ids');}

Combining 2 GET methods (movie collections) into one and displaying them in a single ListView

I'm currently playing around with the TMDB API and I'm trying to combine two maps of GET methods and display them in a single ListView (basically I'm trying to combine two movie collections into one).
Here's the code of the Home Screen of my App (wouldn't call it an app, it's more of a 'training ground').
class _HomeScreenState extends State<HomeScreen> {
List trendingMovies = [];
List topRatedMovies = [];
List tvShows = [];
List genre = [];
List scienceFiction = [];
List starWars = [];
List batman = [];
final String apiKey = 'myapikey';
final String readAccessToken = 'myreadaccesstoken';
int collectionId;
#override
void initState() {
loadMovies();
super.initState();
}
loadMovies()async{
TMDB tmdbWithCustomLogs = TMDB(
ApiKeys(
apiKey,
readAccessToken
),
logConfig: ConfigLogger(
showLogs: true,
showErrorLogs: true
)
);
Map trendingResult = await tmdbWithCustomLogs.v3.trending.getTrending();
Map topRatedResult = await tmdbWithCustomLogs.v3.movies.getTopRated();
Map tvShowsResult = await tmdbWithCustomLogs.v3.tv.getPouplar();
Map genreResult = await tmdbWithCustomLogs.v3.geners.getMovieList();
Map scienceFictionResult = await tmdbWithCustomLogs.v3.movies.getPouplar();
Map starWarsResult = await tmdbWithCustomLogs.v3.collections.getDetails(collectionId = 10);
//THE RELEVANT CODE
Map batmanResult = await tmdbWithCustomLogs.v3.collections.getDetails(collectionId = 120794);
Map batmanResult2 = await tmdbWithCustomLogs.v3.collections.getDetails(collectionId = 263);
//THE RELEVANT CODE ENDS HERE
setState(() {
trendingMovies = trendingResult['results'];
topRatedMovies = topRatedResult['results'];
tvShows = tvShowsResult['results'];
genre = genreResult['genres'];
scienceFiction = scienceFictionResult['results'];
starWars = starWarsResult['parts'];
//THE RELEVANT CODE
batman = [batmanResult['parts'], batmanResult2['parts']];
//THE RELEVANT CODE ENDS HERE
});
print(genre);
}
and here's the code for displaying the ListView
class BatmanMovies extends StatelessWidget {
final List batman;
const BatmanMovies({Key key, this.batman}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ModifiedText(
text: 'I\'m Vengeance, I\'m The Night, I\'m Batman',
size: 25,
weight: FontWeight.bold,
color: Colors.blue,
),
SizedBox(height: 10),
Container(
height: 210,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: batman.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(
title: batman[index]['title'],
bannerURL: 'https://image.tmdb.org/t/p/w500'+batman[index]['backdrop_path'],
posterURL: 'https://image.tmdb.org/t/p/w500'+batman[index]['poster_path'],
synopsis: batman[index]['overview'],
rating: batman[index]['vote_average'].toString(),
releasedOn: batman[index]['release_date'],
id: batman[index]['id'].toString(),
))
);
},
child: Container(
width: 140,
child: Column(
children: [
Container(
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://image.tmdb.org/t/p/w500'+batman[index]['poster_path']
)
)
),
),
],
),
),
);
}
),
)
],
),
);
}
}
but when I run it I get the following error,
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building:
type 'String' is not a subtype of type 'int' of 'index'
When the exception was thrown, this was the stack:
#0 BatmanMovies.build.<anonymous closure> (package:asteric/categories/batman.dart:55:88)
#1 SliverChildBuilderDelegate.build (package:flutter/src/widgets/sliver.dart:455:22)
#2 SliverMultiBoxAdaptorElement._build (package:flutter/src/widgets/sliver.dart:1201:28)
#3 SliverMultiBoxAdaptorElement.createChild.<anonymous closure> (package:flutter/src/widgets/sliver.dart:1214:55)
#4 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2647:19)
...
====================================================================================================
is there something I should add or change into my code or is it impossible to do what I'm trying to do?
Thanks and sorry for my wording, I'm a noob to programming in general.
A Simpler Example
Here is the class MovieDB.
Think of it as the database from where you fetch the movie collection details
class MovieDB {
Map batmanResult = {
'id': 69,
'parts': [
{
"id": 11,
"title": "Star Wars: Episode IV - A New Hope",
},
{
"id": 12,
"title": "Star Wars: Episode V - A Greater Hope",
}
]
};
Map batmanResult2 = {
'id': 420,
'parts': [
{
"id": 1,
"title": "Star Wars: Episode VI - A much needed Hope",
}
]
};
}
With the help of a MovieDB instance db, you do the following :
List batman = [db.batmanResult['parts'], db.batmanResult2['parts']];
Mistake
batman is now a List of List of Map -List<List<Map>> and NOT List<Map>
So instead of batman[0]['id'] (which gives the error you mentioned), you will access the first movie like batman[0][0]['id'], which is pretty cumbersome as you will have to keep a track of number of items in each of the two lists.
Solution
Create a new list batmanMovies, iterate batman and add each movie to that list.
List batmanMovies = [];
batman.forEach((list) {
for (int i = 0; i < list.length; i++) {
batmanMovies.add(list[i]);
}
});
Now you can access each movie correctly :
print(batmanMovies[2]['title'])
Output
Star Wars: Episode VI - Sigh

Throttling keyboard events until child widget is updated

In my simple Flutter desktop image browser I use arrow keys for traversing items in a folder (actually ZIP archive). As loading large images is slower, if arrow keys are pressed multiple times until the image is fully loaded, some images are skipped.
I'd rather waited for all images until fully loaded and to queue key events to some limit (e.g. up to 5 key events).
The actual core snippets:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: RawKeyboardListener(
focusNode: focusNode,
autofocus: true,
onKey: handleKey,
child: InteractiveViewer(
child: image,
),
),
);
}
void handleKey(RawKeyEvent keyEvent) async {
if (keyEvent.isKeyPressed(LogicalKeyboardKey.arrowLeft) ||
keyEvent.isKeyPressed(LogicalKeyboardKey.arrowRight) ||
keyEvent.isKeyPressed(LogicalKeyboardKey.home) ||
keyEvent.isKeyPressed(LogicalKeyboardKey.end)) {
int newImageIndex = currentImageIndex;
if (keyEvent.isKeyPressed(LogicalKeyboardKey.arrowLeft)) {
newImageIndex = max(currentImageIndex - 1, 0);
} else if (keyEvent.isKeyPressed(LogicalKeyboardKey.arrowRight)) {
newImageIndex = min(currentImageIndex + 1, widget.fileNameMap.length - 1);
}
if (newImageIndex != currentImageIndex) {
updateImage(newImageIndex);
}
}
void updateImage(int index) {
setState(() {
File file = new File(paths[index]);
image = new Image.file(file);
});
}
I've found that image skipping on subsequent key pressing can be avoided when the image widget is updated after image data is fully loaded, which can be achieved by ImageProvider this way:
void updateImage(int index) {
File file = new File(paths[index]);
var imageData = new FileImage(file);
imageData
.resolve(ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo _, bool __) {
if (mounted) {
setState(() {
image = new Image(image: imageData);
});
}
}));
}`

flutter_pagewise loads all images at once instead of lazyloading

I'm using Flutter with the package flutter_pagewise to add lazyloading to a large image grid which loads images from local assets.
I created json file which includes all paths for the images. With this file I want to work as future source in flutter_pagewise.
The function which returns the list from json for pagewise
Future<List> getDemoImagesJson(int offset, int limit) async {
String demoImages = await DefaultAssetBundle.of(context)
.loadString("assets/demo/demo.json");
final jsonResult = json.decode(demoImages);
List images = [];
for (var i in jsonResult) {
images.add({"id": 1, "title": "sample", "thumbnailUrl": i});
}
final list = List.from(images).toList();
var listWithOffset = list.skip(offset).toList();
final finalList = listWithOffset.getRange(0, limit).toList();
return ImageModel.fromJsonList(finalList);
}
The ImageModel class (copied from official documentation)
class ImageModel {
String title;
String id;
String thumbnailUrl;
ImageModel.fromJson(obj) {
this.title = obj['title'];
this.id = obj['id'].toString();
this.thumbnailUrl = obj['thumbnailUrl'];
}
static List<ImageModel> fromJsonList(jsonList) {
return jsonList.map<ImageModel>((obj) => ImageModel.fromJson(obj)).toList();
}
}
The PageWise Widget
PagewiseGridView.count(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
pageSize: 10,
crossAxisCount: 3,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
padding: EdgeInsets.all(15.0),
itemBuilder: (context, entry, index) {
return Container(
child: Image.asset(entry.thumbnailUrl,
fit: BoxFit.cover));
},
pageFuture: (pageIndex) {
return getDemoImagesJson(pageIndex * 10, 10);
})
What I expect: I expect that the Widget loads only 10 images and after I scroll down to the end of the page it loads other 10 images...until all images did load.
What I get: The Widget loads all images at once (as batch). There is no lazyload effect after reaching the bottom of the page.
Where is the issue?

Flutter infinite/long list - memory issue and stack overflow error

my use case is to create a list view of articles (each item have the same look, there could be huge amount of articles, e.g. > 10000). I tried with
- ListView with ListView.builder: it supposes only to render the item when the item is displayed
- ScrollController: to determine when to load the next items (pagination)
- then I use List to store the data fetched from restful API using http, by adding the data from http to the List instance
this approach is OK, but in case the user keeps on scrolling pages, the List instance will have more and more items, it can crash with stack Overflow error.
If I don't call List.addAll(), instead I assign the data fetched from api, like: list = data;
I have problem that when the user scroll up, he/she won't be able to see the previous items.
Is there a good approach to solve this? Thanks!
import 'package:flutter/material.dart';
import 'package:app/model.dart';
import 'package:app/components/item.dart';
abstract class PostListPage extends StatefulWidget {
final String head;
DealListPage(this.head);
}
abstract class PostListPageState<T extends PostListPage> extends State<PostListPage> {
final int MAX_PAGE = 2;
DealListPageState(String head) {
this.head = head;
}
final ScrollController scrollController = new ScrollController();
void doInitialize() {
page = 0;
try {
list.clear();
fetchNextPage();
}
catch(e) {
print("Error: " + e.toString());
}
}
#override
void initState() {
super.initState();
this.fetchNextPage();
scrollController.addListener(() {
double maxScroll = scrollController.position.maxScrollExtent;
double currentScroll = scrollController.position.pixels;
double delta = 200.0; // or something else..
if ( maxScroll - currentScroll <= delta) {
fetchNextPage();
}
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
void mergeNewResult(List<PostListItem> result) {
list.addAll(result);
}
Future fetchNextPage() async {
if (!isLoading && mounted) {
page++;
setState(() {
isLoading = true;
});
final List<PostListItem> result = await doFetchData(page);
setState(() {
if (result != null && result.length > 0) {
mergeNewResult(result);
} else {
//TODO show notification
}
isLoading = false;
});
}
}
Future doFetchData(final int page);
String head;
List<PostListItem> list = new List();
var isLoading = false;
int page = 0;
int pageSize = 20;
final int scrollThreshold = 10;
Widget buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
ListView listView = ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext context, int index) {
if (index == list.length) {
return buildProgressIndicator();
}
if (index > 0) {
return Column(
children: [Divider(), PostListItem(list[index])]
);
}
return PostListItem(list[index]);
},
controller: scrollController,
itemCount: list.length
);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(head),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
},
),
// action button
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
},
),
]
),
body: new RefreshIndicator(
onRefresh: handleRefresh,
child: listView
),
);
}
Future<Null> handleRefresh() async {
doInitialize();
return null;
}
}
in my case, when the list length is 600, I start to get stack overflow error like:
I/flutter ( 8842): Another exception was thrown: Stack Overflow
I/flutter ( 8842): Another exception was thrown: Stack Overflow
screen:
enter image description here
somehow flutter doesn't show any more details of the error.
I wrote some sample code for a related question about paginated scrolling, which you could check out.
I didn't implement cache invalidation there, but it would easily be extendable using something like the following in the getPodcast method to remove all items that are more than 100 indexes away from the current location:
for (key in _cache.keys) {
if (abs(key - index) > 100) {
_cache.remove(key);
}
}
An even more sophisticated implementation could take into consideration the scroll velocity and past user behavior to lay out a probability curve (or a simpler Gaussian curve) to fetch content more intelligently.