1
0
mirror of https://github.com/nrop19/weiman_app.git synced 2025-08-03 15:22:47 +08:00
This commit is contained in:
nrop19 2020-01-05 06:08:55 +08:00
parent 582d231063
commit b86adbb991
11 changed files with 323 additions and 313 deletions

View File

@ -1,4 +1,4 @@
# weiman v1.0.4
# weiman v1.0.5
### 微漫脱敏后的开源代码

View File

@ -11,8 +11,9 @@ class ActivityBook extends StatefulWidget {
}
class BookState extends State<ActivityBook> {
static BoxDecoration _border;
final GlobalKey<PullToRefreshNotificationState> _refresh = GlobalKey();
GlobalKey<NestedScrollViewState> _key = GlobalKey<NestedScrollViewState>();
ScrollController _scrollController;
bool _reverse = false;
bool isFavorite = false;
@ -27,6 +28,13 @@ class BookState extends State<ActivityBook> {
SchedulerBinding.instance.addPostFrameCallback((_) {
_refresh.currentState.show();
});
_scrollController = ScrollController();
}
@override
dispose() {
_scrollController.dispose();
super.dispose();
}
Future<bool> loadBook() async {
@ -64,7 +72,7 @@ class BookState extends State<ActivityBook> {
final history = book.chapters
.firstWhere((chapter) => chapter.cid == book.history.cid);
SchedulerBinding.instance.addPostFrameCallback((_) {
_key.currentState.currentInnerPosition.animateTo(
_scrollController.animateTo(
WidgetChapter.height * chapters.indexOf(history).toDouble(),
duration: Duration(milliseconds: 500),
curve: Curves.linear);
@ -93,70 +101,6 @@ class BookState extends State<ActivityBook> {
});
}
List<Widget> _headerBuilder(BuildContext context, bool innerBoxIsScrolled) {
Color color = isFavorite ? Colors.red : Colors.white;
IconData icon = isFavorite ? Icons.favorite : Icons.favorite_border;
final book = this.book ?? widget.book;
return <Widget>[
SliverAppBar(
floating: true,
pinned: true,
snap: false,
title: Text(widget.book.name),
expandedHeight: 200,
actions: <Widget>[
IconButton(
onPressed: _sort,
icon: Icon(_reverse
? FontAwesomeIcons.sortNumericDown
: FontAwesomeIcons.sortNumericDownAlt)),
IconButton(onPressed: favoriteBook, icon: Icon(icon, color: color))
],
flexibleSpace: FlexibleSpaceBar(
background: SafeArea(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin:
EdgeInsets.only(top: 50, left: 20, right: 10, bottom: 20),
height: 160,
child: Hero(
tag: widget.heroTag,
child: Image.network(
widget.book.avatar,
),
),
),
Expanded(
child: Container(
padding: EdgeInsets.only(top: 50, right: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'作者:' + (book.author ?? ''),
style: TextStyle(color: Colors.white),
),
Container(
margin: EdgeInsets.only(top: 10),
),
Text(
'简介:\n' + (book.description ?? ''),
softWrap: true,
style: TextStyle(color: Colors.white, height: 1.2),
),
],
),
)),
],
),
),
),
)
];
}
List<Widget> chapterWidgets() {
final book = this.book ?? widget.book;
List<Widget> list = [];
@ -175,6 +119,16 @@ class BookState extends State<ActivityBook> {
final book = this.book ?? widget.book;
final chapter = chapters[index];
final isRead = chapter.cid == book.history?.cid;
if (index < chapters.length - 1) {
return DecoratedBox(
decoration: _border,
child: WidgetChapter(
chapter: chapter,
onTap: _openChapter,
read: isRead,
),
);
}
return WidgetChapter(
chapter: chapter,
onTap: _openChapter,
@ -184,6 +138,10 @@ class BookState extends State<ActivityBook> {
@override
Widget build(BuildContext context) {
if (_border == null)
_border = BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context, color: Colors.grey)));
Color color = isFavorite ? Colors.red : Colors.white;
IconData icon = isFavorite ? Icons.favorite : Icons.favorite_border;
final book = this.book ?? widget.book;
@ -192,16 +150,12 @@ class BookState extends State<ActivityBook> {
key: _refresh,
onRefresh: loadBook,
maxDragOffset: kToolbarHeight * 2,
child: NestedScrollView(
key: _key,
headerSliverBuilder: (_, __) => [],
physics: AlwaysScrollableClampingScrollPhysics(),
body: CustomScrollView(
physics: AlwaysScrollableClampingScrollPhysics(),
child: CustomScrollView(
controller: _scrollController,
slivers: [
SliverAppBar(
floating: true,
pinned: false,
pinned: true,
title: Text(widget.book.name),
expandedHeight: 200,
actions: <Widget>[
@ -224,9 +178,8 @@ class BookState extends State<ActivityBook> {
height: 160,
child: Hero(
tag: widget.heroTag,
child: Image.network(
widget.book.avatar,
),
child:
Image(image: NetworkImageSSL(widget.book.avatar)),
),
),
Expanded(
@ -261,71 +214,15 @@ class BookState extends State<ActivityBook> {
onTap: () => _refresh.currentState
.show(notificationDragOffset: kToolbarHeight * 2),
)),
NestedScrollViewInnerScrollPositionKeyWidget(
Key('0'),
SliverList(
delegate: SliverChildBuilderDelegate(
buildChapter,
childCount: book.chapters.length,
),
),
),
],
),
),
),
);
}
Widget build1(BuildContext context) {
final double statusBarHeight = MediaQuery.of(context).padding.top;
var pinnedHeaderHeight =
//statusBar height
statusBarHeight +
//pinned SliverAppBar height in header
kToolbarHeight;
return Scaffold(
body: NestedScrollViewRefreshIndicator(
key: _refresh,
onRefresh: loadBook,
child: NestedScrollView(
key: _key,
pinnedHeaderSliverHeightBuilder: () => pinnedHeaderHeight,
headerSliverBuilder: _headerBuilder,
body: LayoutBuilder(
builder: (_, __) {
if (isLoading)
return Container();
else if (isSuccess) {
return ListView(
children: ListTile.divideTiles(
context: context,
color: Colors.grey,
tiles: chapterWidgets())
.toList());
}
return Container(
constraints: BoxConstraints.expand(),
alignment: Alignment.center,
child: Center(
child: Text(
'读取失败,下拉刷新\n如果多次失败,请检查网络',
textAlign: TextAlign.center,
),
),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_key.currentState.currentInnerPosition.animateTo(0,
duration: Duration(milliseconds: 100), curve: Curves.linear);
},
child: Icon(FontAwesomeIcons.angleDoubleUp),
),
);
}
}

View File

@ -117,6 +117,12 @@ class _ChapterDrawer extends State<ChapterDrawer> {
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void updateRead() {
final readChapter = widget.book.chapters
.firstWhere((chapter) => widget.book.history?.cid == chapter.cid);
@ -134,12 +140,6 @@ class _ChapterDrawer extends State<ChapterDrawer> {
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Drawer(
@ -183,6 +183,9 @@ class ChapterContentView extends StatefulWidget {
class _ChapterContentView extends State<ChapterContentView> {
final GlobalKey<PullToRefreshNotificationState> _refresh = GlobalKey();
final List<String> images = [];
TextStyle _style = TextStyle(color: Colors.white);
BoxDecoration _decoration =
BoxDecoration(color: Colors.black.withOpacity(0.4));
int chapterIndex = -1;
bool hasNextChapter = false;
@ -223,6 +226,30 @@ class _ChapterContentView extends State<ChapterContentView> {
@override
Widget build(BuildContext context) {
final List<Widget> list = [];
for (var i = 0; i < images.length; i++) {
list.add(SliverStickyHeader(
overlapsContent: true,
header: SafeArea(
top: true,
bottom: false,
child: Row(
children: [
Container(
padding: EdgeInsets.all(5),
decoration: _decoration,
child: Text(
'${i + 1} / ${images.length}',
style: _style,
),
),
],
),
),
sliver:
SliverToBoxAdapter(child: Image(image: NetworkImageSSL(images[i]))),
));
}
return PullToRefreshNotification(
key: _refresh,
onRefresh: fetchImages,
@ -236,16 +263,14 @@ class _ChapterContentView extends State<ChapterContentView> {
floating: true,
actions: widget.actions,
),
PullToRefreshContainer((info) => SliverPullToRefreshHeader(
PullToRefreshContainer(
(info) => SliverPullToRefreshHeader(
info: info,
onTap: () => _refresh.currentState
.show(notificationDragOffset: kToolbarHeight * 2),
)),
SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, i) => Image.network(images[i]),
childCount: images.length),
),
),
...list,
],
),
);

View File

@ -25,6 +25,7 @@ class HomeState extends State<ActivityHome> {
///
SchedulerBinding.instance.addPostFrameCallback((_) async {
autoSwitchTheme();
_FavoriteList.getBooks();
await _FavoriteList.checkNews();
final updated = _FavoriteList.hasNews.values
@ -39,6 +40,14 @@ class HomeState extends State<ActivityHome> {
});
}
void autoSwitchTheme() async {
final isDark = await DynamicTheme.of(context).loadBrightness();
final nowIsDark = DynamicTheme.of(context).brightness == Brightness.dark;
if (isDark != nowIsDark)
DynamicTheme.of(context)
.setBrightness(isDark ? Brightness.dark : Brightness.light);
}
void gotoSearch() {
Navigator.push(
context,
@ -66,9 +75,8 @@ class HomeState extends State<ActivityHome> {
@override
Widget build(BuildContext context) {
var media = MediaQuery.of(context);
var width = media.size.width;
width = width * .8;
final media = MediaQuery.of(context);
final width = (media.size.width * 0.8).roundToDouble();
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
@ -183,11 +191,13 @@ class HomeState extends State<ActivityHome> {
),
],
),
Quick(
Center(
child: Quick(
key: _quickState,
width: width,
draggableModeChanged: _draggableModeChanged,
),
),
Container(
margin: EdgeInsets.only(bottom: 10),
child: Text(
@ -238,21 +248,3 @@ class HomeState extends State<ActivityHome> {
);
}
}
Iterable<Widget> favoriteTiles(context, Iterable<Book> books,
{void Function(Book book) onTap}) {
return books.map((book) => ListTile(
onTap: () {
onTap(book);
},
title: Text(book.name),
leading: Image.network(book.avatar),
subtitle: Text(
'作者:' + book.author,
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
));
}

View File

@ -69,7 +69,12 @@ class SearchState extends State<Search> {
},
child: TextField(
decoration: InputDecoration(
hintText: '搜索书名', prefixIcon: Icon(Icons.search)),
hintText: '搜索书名',
prefixIcon: IconButton(
onPressed: startSearch,
icon: Icon(Icons.search),
),
),
textAlign: TextAlign.left,
controller: _controller,
autofocus: true,

View File

@ -1,7 +1,6 @@
part of '../main.dart';
const domain = '';
final host = Uri.parse(domain).host;
class UserAgentClient extends http.BaseClient {
final String userAgent;

View File

@ -0,0 +1,124 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart' as image_provider;
/// The dart:io implementation of [image_provider.NetworkImage].
class NetworkImageSSL
extends image_provider.ImageProvider<image_provider.NetworkImage>
implements image_provider.NetworkImage {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
const NetworkImageSSL(this.url, {this.scale = 1.0, this.headers})
: assert(url != null),
assert(scale != null);
@override
final String url;
@override
final double scale;
@override
final Map<String, String> headers;
@override
Future<NetworkImageSSL> obtainKey(
image_provider.ImageConfiguration configuration) {
return SynchronousFuture<NetworkImageSSL>(this);
}
@override
image_provider.ImageStreamCompleter load(
image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<image_provider.ImageChunkEvent> chunkEvents =
StreamController<image_provider.ImageChunkEvent>();
return image_provider.MultiFrameImageStreamCompleter(
codec: _loadAsync(key, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
informationCollector: () {
return <DiagnosticsNode>[
DiagnosticsProperty<image_provider.ImageProvider>(
'Image provider', this),
DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
];
},
);
}
// Do not access this field directly; use [_httpClient] instead.
// We set `autoUncompress` to false to ensure that we can trust the value of
// the `Content-Length` HTTP header. We automatically uncompress the content
// in our call to [consolidateHttpClientResponseBytes].
static final HttpClient _sharedHttpClient = HttpClient()
..autoUncompress = false
..badCertificateCallback = (_, __, ___) => true;
static HttpClient get _httpClient {
HttpClient client = _sharedHttpClient;
assert(() {
if (image_provider.debugNetworkImageHttpClientProvider != null)
client = image_provider.debugNetworkImageHttpClientProvider();
return true;
}());
return client;
}
Future<ui.Codec> _loadAsync(
NetworkImageSSL key,
StreamController<image_provider.ImageChunkEvent> chunkEvents,
image_provider.DecoderCallback decode,
) async {
try {
assert(key == this);
final Uri resolved = Uri.base.resolve(key.url);
final HttpClientRequest request = await _httpClient.getUrl(resolved);
headers?.forEach((String name, String value) {
request.headers.add(name, value);
});
final HttpClientResponse response = await request.close();
if (response.statusCode != HttpStatus.ok)
throw image_provider.NetworkImageLoadException(
statusCode: response.statusCode, uri: resolved);
final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (int cumulative, int total) {
chunkEvents.add(image_provider.ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
));
},
);
if (bytes.lengthInBytes == 0)
throw Exception('NetworkImage is an empty file: $resolved');
return decode(bytes);
} finally {
chunkEvents.close();
}
}
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType) return false;
final NetworkImageSSL typedOther = other;
return url == typedOther.url && scale == typedOther.scale;
}
@override
int get hashCode => ui.hashValues(url, scale);
@override
String toString() => '$runtimeType("$url", scale: $scale)';
}

View File

@ -8,7 +8,6 @@ import 'package:async/async.dart';
import 'package:draggable_container/draggable_container.dart';
import 'package:dynamic_theme/dynamic_theme.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:flutter/material.dart' hide NestedScrollView;
@ -26,6 +25,7 @@ import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart'
hide CircularProgressIndicator;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'classes/networkImageSSL.dart';
part './activities/book.dart';
@ -85,15 +85,6 @@ void main() async {
UserAgentClient.init(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36');
runApp(Main(packageInfo: packageInfo));
// runApp(MaterialApp(
// title: '微漫',
// theme: ThemeData.light(),
// darkTheme: ThemeData.dark(),
// themeMode: ThemeMode.system,
// debugShowCheckedModeBanner: false,
// navigatorObservers: [observer],
// home: ActivityHome(packageInfo),
// ));
}
class Main extends StatefulWidget {
@ -106,24 +97,10 @@ class Main extends StatefulWidget {
}
class _Main extends State<Main> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DynamicTheme(
defaultBrightness: ThemeMode.system == ThemeMode.light
? Brightness.light
: Brightness.dark,
defaultBrightness: Brightness.dark,
data: (brightness) => new ThemeData(
brightness: brightness,
),
@ -148,10 +125,4 @@ class _Main extends State<Main> with WidgetsBindingObserver {
// home: ActivityHome(widget.packageInfo),
// );
}
@override
void didChangePlatformBrightness() {
print('改变亮度');
setState(() {});
}
}

View File

@ -29,8 +29,8 @@ class WidgetBook extends StatelessWidget {
dense: true,
leading: Hero(
tag: 'bookAvatar${book.aid}',
child: Image.network(
book.avatar,
child: Image(image:NetworkImageSSL(
book.avatar),
height: 200,
fit: BoxFit.scaleDown,
)),
@ -48,7 +48,7 @@ class WidgetBook extends StatelessWidget {
}
class WidgetChapter extends StatelessWidget {
static final double height = 56;
static final double height = kToolbarHeight;
final Chapter chapter;
final Function(Chapter) onTap;
final bool read;
@ -83,8 +83,8 @@ class WidgetChapter extends StatelessWidget {
softWrap: true,
maxLines: 2,
),
leading: Image.network(
chapter.avatar,
leading: Image(image:NetworkImageSSL(
chapter.avatar),
fit: BoxFit.fitWidth,
width: 100,
),
@ -106,8 +106,8 @@ class WidgetHistory extends StatelessWidget {
if (onTap != null) onTap(book);
},
title: Text(book.name),
leading: Image.network(
book.avatar,
leading: Image(image:NetworkImageSSL(
book.avatar),
fit: BoxFit.fitHeight,
),
subtitle: Text(book.history.cname),
@ -173,7 +173,7 @@ class _WidgetBookCheckNew extends State<WidgetBookCheckNew> {
openBook(context, widget.book, 'checkBook${widget.book.aid}'),
leading: Hero(
tag: 'checkBook${widget.book.aid}',
child: Image.network(widget.book.avatar),
child: Image(image:NetworkImageSSL(widget.book.avatar)),
),
dense: true,
isThreeLine: true,

View File

@ -174,7 +174,7 @@ class FBookItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListTile(
onTap: () => onTap(book),
leading: Hero(tag: 'fb ${book.aid}', child: Image.network(book.avatar)),
leading: Hero(tag: 'fb ${book.aid}', child: Image(image:NetworkImageSSL(book.avatar))),
title: Text(book.name, style: Theme.of(context).textTheme.body1),
subtitle: RichText(text: subtitle),
);

View File

@ -21,7 +21,7 @@ class QuickBook extends DraggableItem {
children: <Widget>[
Hero(
tag: '$heroTag ${book.aid}',
child: Image.network(book.avatar),
child: Image(image: NetworkImageSSL(book.avatar)),
),
Positioned(
left: 0,
@ -79,7 +79,7 @@ class QuickState extends State<Quick> {
.where((book) => !id.contains(book.aid))
.map((book) => ListTile(
title: Text(book.name),
leading: Image.network(book.avatar),
leading: Image(image: NetworkImageSSL(book.avatar)),
onTap: () {
Navigator.pop(context, book);
},
@ -144,8 +144,6 @@ class QuickState extends State<Quick> {
}
int length() {
// print(_key.currentState.items);
// return 0;
return _key.currentState.items.where((item) => item is QuickBook).length;
}
@ -165,6 +163,8 @@ class QuickState extends State<Quick> {
@override
Widget build(BuildContext context) {
final width = widget.width / 4 - 10;
final height = (width / 0.7).roundToDouble();
return Column(
children: <Widget>[
Container(
@ -176,14 +176,11 @@ class QuickState extends State<Quick> {
style: TextStyle(color: Colors.grey, fontSize: 12),
),
),
Container(
width: widget.width,
child: DraggableContainer(
DraggableContainer(
key: _key,
slotMargin: EdgeInsets.only(bottom: 8, left: 7, right: 7),
slotSize: Size(72, 100),
slotDecoration:
BoxDecoration(color: Colors.grey.withOpacity(0.3)),
slotMargin: EdgeInsets.only(bottom: 8, left: 6, right: 6),
slotSize: Size(width, height),
slotDecoration: BoxDecoration(color: Colors.grey.withOpacity(0.3)),
dragDecoration: BoxDecoration(
boxShadow: [BoxShadow(color: Colors.black, blurRadius: 10)]),
items: _draggableItems,
@ -198,17 +195,17 @@ class QuickState extends State<Quick> {
final buttonIndex = items.indexOf(_addButton);
print('null $nullIndex, button $buttonIndex');
if (nullIndex > -1 && buttonIndex == -1) {
_key.currentState.insteadOfIndex(nullIndex, _addButton,
triggerEvent: false);
_key.currentState
.insteadOfIndex(nullIndex, _addButton, triggerEvent: false);
} else if (nullIndex > -1 &&
buttonIndex > -1 &&
nullIndex < buttonIndex) {
_key.currentState.removeItem(_addButton);
_key.currentState.insteadOfIndex(nullIndex, _addButton,
triggerEvent: false);
_key.currentState
.insteadOfIndex(nullIndex, _addButton, triggerEvent: false);
}
},
)),
),
],
);
}