Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_sentry/flutter_sentry.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:provide/provide.dart';
import 'package:provider/provider.dart';

void main() {
FlutterSentry.wrap(() {
Expand All @@ -21,18 +21,21 @@ void main() {
//
// In this case, the ComicslateClient gets instantiated
// the first time someone uses it, and lives as a singleton after that.
final providers = Providers()
..provide(Provider.value(ComicslateClient(
language: 'ru',
offlineStorage: FlutterCachingAPIClient(
cacheName: 'comicslate-client-json-v1',
responseParser: (js) => json.decode(utf8.decode(js))),
prefetchCache: FlutterCachingAPIClient(
cacheName: 'comicslate-client-images',
responseParser: (bytes) => bytes),
)));
final providers = [
Provider<ComicslateClient>(
create: (_) => ComicslateClient(
language: 'ru',
offlineStorage: FlutterCachingAPIClient(
cacheName: 'comicslate-client-json',
responseParser: (js) => json.decode(utf8.decode(js))),
prefetchCache: FlutterCachingAPIClient(
cacheName: 'comicslate-client-images',
responseParser: (bytes) => bytes),
),
),
];

runApp(ProviderNode(providers: providers, child: MyApp()));
runApp(MultiProvider(providers: providers, child: MyApp()));
}, dsn: 'https://[email protected]/5230711');
}

Expand Down
2 changes: 1 addition & 1 deletion lib/models/storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class FlutterCachingAPIClient<T> extends BaseCacheManager
try {
await _get(url, headers: headers)
.last
.timeout(const Duration(seconds: 5));
.timeout(const Duration(seconds: 10));
} finally {
_currentlyPrefetching.remove(url);
}
Expand Down
6 changes: 2 additions & 4 deletions lib/view/comic_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'package:comicslate/view_model/comic_page_view_model.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:provide/provide.dart';
import 'package:provider/provider.dart';

class _ComicslateTitleWidget extends StatelessWidget {
static const _numberOfLogos = 9;
Expand All @@ -30,9 +30,7 @@ class _ComicslateTitleWidget extends StatelessWidget {
class ComicList extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO(ksheremet): Create Inherited widget for bloc. Consider to try
// ScopedModel, Provide
final bloc = ComicListBloc(Provide.value<ComicslateClient>(context));
final bloc = ComicListBloc(context.watch<ComicslateClient>());
return Scaffold(
appBar: SearchBarWidget(
title: _ComicslateTitleWidget(),
Expand Down
122 changes: 58 additions & 64 deletions lib/view/comic_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:math';
import 'dart:typed_data';

import 'package:comicslate/models/comic_strip.dart';
import 'package:comicslate/models/comicslate_client.dart';
Expand All @@ -8,10 +7,10 @@ import 'package:comicslate/view/helpers/strip_image.dart';
import 'package:comicslate/view_model/comic_page_view_model.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:provide/provide.dart';
import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:share/share.dart';
import 'package:wakelock/wakelock.dart';

Expand Down Expand Up @@ -80,7 +79,7 @@ class ComicPage extends StatelessWidget {
),
// Get a list of stripsId
body: FutureBuilder<Iterable<String>>(
future: Provide.value<ComicslateClient>(context)
future: Provider.of<ComicslateClient>(context)
.getStoryStripsList(
ComicPageViewModelWidget.of(context).viewModel.comic)
.first,
Expand Down Expand Up @@ -184,18 +183,24 @@ class StripPage extends StatefulWidget {
}

class _StripPageState extends State<StripPage> {
PageController _controller;
bool _isOrientationSetup = false;
Future<int> _lastSeenStrip;
bool _allowCache = true;
int _lastSeenStripValue;

final ItemScrollController _controller = ItemScrollController();
final ItemPositionsListener _itemPositionsListener =
ItemPositionsListener.create();

@override
void initState() {
// Do not allow the screen to go to sleep when dislaying a comic strip.
Wakelock.enable();

widget.viewModel.doGoToPage.listen((page) {
_controller.jumpToPage(page);
_controller.scrollTo(
index: page,
duration: const Duration(seconds: 2),
curve: Curves.easeIn);
});

widget.viewModel.doRefreshStrip.listen((_) {
Expand All @@ -204,22 +209,49 @@ class _StripPageState extends State<StripPage> {
});
});

_itemPositionsListener.itemPositions.addListener(() {
if (_itemPositionsListener.itemPositions.value.first.index !=
_lastSeenStripValue) {
_lastSeenStripValue =
_itemPositionsListener.itemPositions.value.first.index;

FirebaseAnalytics().logViewItem(
itemCategory: widget.viewModel.comic.id,
itemId: _lastSeenStripValue.toString(),
itemName: _lastSeenStripValue.toString(),
);

widget.viewModel.setLastSeenPage(_lastSeenStripValue);
}
});

_lastSeenStrip = widget.viewModel.getLastSeenPage();

super.initState();
}

@override
Widget build(BuildContext context) => FutureBuilder<int>(
future: _lastSeenStrip,
builder: (context, snapshot) {
if (snapshot.hasData) {
_controller = PageController(initialPage: snapshot.data);
return PageView.builder(
physics: const AlwaysScrollableScrollPhysics(),
controller: _controller,
future: _lastSeenStrip,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return InteractiveViewer(
minScale: 0.1,
maxScale: 5,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ScrollablePositionedList.builder(
initialScrollIndex: snapshot.data,
scrollDirection: Axis.vertical,
itemScrollController: _controller,
itemPositionsListener: _itemPositionsListener,
itemCount: widget.viewModel.stripIds.length,
itemBuilder: (context, i) => FutureBuilder<ComicStrip>(
future: Provide.value<ComicslateClient>(context)
future: Provider.of<ComicslateClient>(context)
.getStrip(
widget.viewModel.comic,
widget.viewModel.stripIds.elementAt(i),
Expand All @@ -241,71 +273,33 @@ class _StripPageState extends State<StripPage> {
return Center(
child: Text(
'Данная страница '
'${widget.viewModel.stripIds.elementAt(i)} еще не '
'поддерживается мобильным приложением: $title.',
'${widget.viewModel.stripIds.elementAt(i)} еще '
'не поддерживается мобильным приложением: '
'$title.',
),
);
} else {
if (!_isOrientationSetup) {
setUpOrientation(stripSnapshot.data.imageBytes);
}
widget.viewModel.currentStrip = stripSnapshot.data;
widget.viewModel.currentStripId =
widget.viewModel.stripIds.elementAt(i);
// TODO(ksheremet): Zoomable widget doesn't work
// in Column
return StripImage(
viewModel: widget.viewModel,
);
}
} else {
return const Center(child: CircularProgressIndicator());
return SizedBox(
height: MediaQuery.of(context).size.height / 5,
child:
const Center(child: CircularProgressIndicator()));
}
}),
onPageChanged: (index) {
FirebaseAnalytics().logViewItem(
itemCategory: widget.viewModel.comic.id,
itemId: index.toString(),
itemName: index.toString(),
);

widget.viewModel.setLastSeenPage(index);
},
);
} else {
return const CircularProgressIndicator();
}
},
);

// TODO(ksheremet): Consider more elegant solution, doesnt' work on iOS
void setUpOrientation(Uint8List imageBytes) {
final image = MemoryImage(imageBytes);
image
.resolve(createLocalImageConfiguration(context))
.addListener(ImageStreamListener((imageInfo, synchronousCall) {
_isOrientationSetup = true;
if (imageInfo.image.width > imageInfo.image.height) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight
]);
} else {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
}
}));
}
),
),
);
});

@override
void dispose() {
// When we leave the screen enable screen rotation
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
Wakelock.disable();
super.dispose();
}
Expand Down
12 changes: 4 additions & 8 deletions lib/view/helpers/comic_card.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:comicslate/models/comic.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_networkimage/provider.dart';
import 'package:flutter_advanced_networkimage/transition.dart';

@immutable
class ComicsRatingWidget extends StatefulWidget {
Expand Down Expand Up @@ -111,13 +110,10 @@ class ComicCard extends StatelessWidget {
} else {
cover = Column(children: [
Expanded(
child: TransitionToImage(
image: AdvancedNetworkImage(
imageUrl.toString(),
useDiskCache: true,
),
child: CachedNetworkImage(
imageUrl: imageUrl.toString(),
fit: BoxFit.cover,
placeholder: Image.asset('images/favicon.webp'),
placeholder: (_, __) => Image.asset('images/favicon.webp'),
),
),
Container(
Expand Down
28 changes: 13 additions & 15 deletions lib/view/helpers/strip_image.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:comicslate/view_model/comic_page_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_networkimage/zoomable.dart';

class StripImage extends StatelessWidget {
final ComicPageViewModel viewModel;
Expand All @@ -13,19 +12,18 @@ class StripImage extends StatelessWidget {
if (viewModel.currentStrip.title != null) {
aboutStrip = aboutStrip + viewModel.currentStrip.title;
}
return ZoomableWidget(
enableRotate: false,
maxScale: 3,
zoomSteps: 2,
multiFingersPan: true,
singleFingerPan: false,
minScale: 1,
child: Column(children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text(aboutStrip),
),
Expanded(child: Image.memory(viewModel.currentStrip.imageBytes)),
]));
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text(aboutStrip),
),
Image.memory(
viewModel.currentStrip.imageBytes,
fit: BoxFit.fitHeight,
),
],
);
}
}
Loading