网络文章-v1
This commit is contained in:
@@ -47,7 +47,7 @@ android {
|
|||||||
applicationId "com.toly1994.flutter_first_station"
|
applicationId "com.toly1994.flutter_first_station"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
minSdkVersion flutter.minSdkVersion
|
minSdkVersion 19
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
|||||||
@@ -14,14 +14,7 @@ class GuessAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
leading: Icon(Icons.menu, color: Colors.black),
|
||||||
statusBarIconBrightness: Brightness.dark,
|
|
||||||
statusBarColor: Colors.transparent),
|
|
||||||
titleSpacing: 0,
|
|
||||||
leading: Icon(
|
|
||||||
Icons.menu,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
@@ -31,8 +24,6 @@ class GuessAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
title: TextField(
|
title: TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'counter/counter_page.dart';
|
import 'counter/counter_page.dart';
|
||||||
import 'guess/guess_page.dart';
|
import 'guess/guess_page.dart';
|
||||||
@@ -23,6 +24,17 @@ class MyApp extends StatelessWidget {
|
|||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
elevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
titleTextStyle: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
iconTheme: IconThemeData(color: Colors.black),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
home: const AppNavigation(),
|
home: const AppNavigation(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,18 +12,12 @@ class MuyuAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
elevation: 0,
|
|
||||||
centerTitle: true,
|
|
||||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
|
||||||
statusBarIconBrightness: Brightness.dark,
|
|
||||||
statusBarColor: Colors.transparent),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
titleTextStyle: const TextStyle(
|
|
||||||
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
iconTheme: const IconThemeData(color: Colors.black),
|
|
||||||
title: const Text("电子木鱼"),
|
title: const Text("电子木鱼"),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(onPressed: onTapHistory, icon: const Icon(Icons.history))
|
IconButton(
|
||||||
|
onPressed: onTapHistory,
|
||||||
|
icon: const Icon(Icons.history),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_first_station/guess/guess_page.dart';
|
|||||||
import 'package:flutter_first_station/muyu/muyu_page.dart';
|
import 'package:flutter_first_station/muyu/muyu_page.dart';
|
||||||
import 'package:flutter_first_station/paper/paper.dart';
|
import 'package:flutter_first_station/paper/paper.dart';
|
||||||
|
|
||||||
|
import '../net_article/views/net_articel_page.dart';
|
||||||
import 'app_bottom_bar.dart';
|
import 'app_bottom_bar.dart';
|
||||||
|
|
||||||
class AppNavigation extends StatefulWidget {
|
class AppNavigation extends StatefulWidget {
|
||||||
@@ -20,21 +21,18 @@ class _AppNavigationState extends State<AppNavigation> {
|
|||||||
MenuData(label: '猜数字', icon: Icons.question_mark),
|
MenuData(label: '猜数字', icon: Icons.question_mark),
|
||||||
MenuData(label: '电子木鱼', icon: Icons.my_library_music_outlined),
|
MenuData(label: '电子木鱼', icon: Icons.my_library_music_outlined),
|
||||||
MenuData(label: '白板绘制', icon: Icons.palette_outlined),
|
MenuData(label: '白板绘制', icon: Icons.palette_outlined),
|
||||||
|
MenuData(label: '网络文章', icon: Icons.article_outlined),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Scaffold(
|
||||||
children: [
|
body: _buildContent(),
|
||||||
Expanded(
|
bottomNavigationBar: AppBottomBar(
|
||||||
child: _buildContent(),
|
currentIndex: _index,
|
||||||
),
|
onItemTap: _onChangePage,
|
||||||
AppBottomBar(
|
menus: menus,
|
||||||
currentIndex: _index,
|
),
|
||||||
onItemTap: _onChangePage,
|
|
||||||
menus: menus,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +48,11 @@ class _AppNavigationState extends State<AppNavigation> {
|
|||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
controller: _ctrl,
|
controller: _ctrl,
|
||||||
children: const [
|
children: const [
|
||||||
GuessPage(),
|
GuessPage(),
|
||||||
MuyuPage(),
|
MuyuPage(),
|
||||||
Paper(),
|
Paper(),
|
||||||
|
NetArticlePage(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
23
lib/net_article/api/article_api.dart
Normal file
23
lib/net_article/api/article_api.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import '../model/article.dart';
|
||||||
|
|
||||||
|
class ArticleApi{
|
||||||
|
|
||||||
|
static const String kBaseUrl = 'https://www.wanandroid.com';
|
||||||
|
|
||||||
|
final Dio _client = Dio(BaseOptions(baseUrl: kBaseUrl));
|
||||||
|
|
||||||
|
Future<List<Article>> loadArticles(int page) async {
|
||||||
|
String path = '/article/list/$page/json';
|
||||||
|
var rep = await _client.get(path);
|
||||||
|
if (rep.statusCode == 200) {
|
||||||
|
if(rep.data!=null){
|
||||||
|
var data = rep.data['data']['datas'] as List;
|
||||||
|
return data.map(Article.formMap).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
24
lib/net_article/model/article.dart
Normal file
24
lib/net_article/model/article.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
class Article {
|
||||||
|
final String title;
|
||||||
|
final String url;
|
||||||
|
final String time;
|
||||||
|
|
||||||
|
const Article({
|
||||||
|
required this.title,
|
||||||
|
required this.time,
|
||||||
|
required this.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Article.formMap(dynamic json) {
|
||||||
|
return Article(
|
||||||
|
title: json['title'] ?? '未知',
|
||||||
|
url: json['link'] ?? '',
|
||||||
|
time: json['niceDate'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Article{title: $title, url: $url, time: $time}';
|
||||||
|
}
|
||||||
|
}
|
||||||
114
lib/net_article/views/article_content.dart
Normal file
114
lib/net_article/views/article_content.dart
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_first_station/net_article/api/article_api.dart';
|
||||||
|
import '../model/article.dart';
|
||||||
|
import 'article_detail_page.dart';
|
||||||
|
|
||||||
|
class ArticleContent extends StatefulWidget {
|
||||||
|
const ArticleContent({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ArticleContent> createState() => _ArticleContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArticleContentState extends State<ArticleContent> {
|
||||||
|
List<Article> _articles = [];
|
||||||
|
ArticleApi api = ArticleApi();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadData() async {
|
||||||
|
_articles = await api.loadArticles(0);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemExtent: 80,
|
||||||
|
itemCount: _articles.length,
|
||||||
|
itemBuilder: _buildItemByIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItemByIndex(BuildContext context, int index) {
|
||||||
|
return ArticleItem(
|
||||||
|
article: _articles[index],
|
||||||
|
onTap: _jumpToPage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _jumpToPage(Article article) {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => ArticleDetailPage(article: article),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArticleItem extends StatelessWidget {
|
||||||
|
final Article article;
|
||||||
|
final ValueChanged<Article> onTap;
|
||||||
|
|
||||||
|
const ArticleItem({Key? key, required this.article, required this.onTap})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => onTap(article),
|
||||||
|
child: Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
article.title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
// Spacer(),
|
||||||
|
Text(
|
||||||
|
article.time,
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
article.url,
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
lib/net_article/views/article_detail_page.dart
Normal file
32
lib/net_article/views/article_detail_page.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_first_station/net_article/model/article.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
class ArticleDetailPage extends StatefulWidget {
|
||||||
|
final Article article;
|
||||||
|
|
||||||
|
const ArticleDetailPage({Key? key, required this.article}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ArticleDetailPage> createState() => _ArticleDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArticleDetailPageState extends State<ArticleDetailPage> {
|
||||||
|
late WebViewController controller;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
controller = WebViewController()
|
||||||
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
|
..setBackgroundColor(const Color(0x00000000))
|
||||||
|
..loadRequest(Uri.parse(widget.article.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(widget.article.title)),
|
||||||
|
body: WebViewWidget(controller: controller),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lib/net_article/views/net_articel_page.dart
Normal file
18
lib/net_article/views/net_articel_page.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'article_content.dart';
|
||||||
|
|
||||||
|
class NetArticlePage extends StatelessWidget {
|
||||||
|
const NetArticlePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('网络请求测试'),
|
||||||
|
),
|
||||||
|
body: ArticleContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,30 +17,17 @@ class PaperAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
centerTitle: true,
|
|
||||||
elevation: 0,
|
|
||||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
|
||||||
statusBarIconBrightness: Brightness.dark,
|
|
||||||
statusBarColor: Colors.transparent),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
leading: BackUpButtons(
|
leading: BackUpButtons(
|
||||||
onBack: onBack,
|
onBack: onBack,
|
||||||
onRevocation: onRevocation,
|
onRevocation: onRevocation,
|
||||||
),
|
),
|
||||||
leadingWidth: 100,
|
leadingWidth: 100,
|
||||||
title: Text(
|
title: Text('画板绘制'),
|
||||||
'画板绘制',
|
|
||||||
style: TextStyle(color: Colors.black, fontSize: 16),
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
onPressed: onClear,
|
onPressed: onClear,
|
||||||
icon: Icon(
|
icon: Icon(CupertinoIcons.delete, size: 20))
|
||||||
CupertinoIcons.delete,
|
|
||||||
color: Colors.black,
|
|
||||||
size: 20,
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
40
pubspec.lock
40
pubspec.lock
@@ -113,6 +113,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: "347d56c26d63519552ef9a569f2a593dda99a81fdbdff13c584b7197cfe05059"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.2"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -485,6 +493,38 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
webview_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: webview_flutter
|
||||||
|
sha256: "1a37bdbaaf5fbe09ad8579ab09ecfd473284ce482f900b5aea27cf834386a567"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
|
webview_flutter_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_android
|
||||||
|
sha256: "1acea8def62592123e2fbbca164ed8681a98a890bdcbb88f916d5b4a22687759"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.7.0"
|
||||||
|
webview_flutter_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_platform_interface
|
||||||
|
sha256: "78715dc442b7849dbde74e92bb67de1cecf5addf95531c5fb474e72f5fe9a507"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
webview_flutter_wkwebview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_wkwebview
|
||||||
|
sha256: "4646bb68297803bdbb96d46853e8fcb560d6cb5e04153fa64581535767875dfe"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.3"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ dependencies:
|
|||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
shared_preferences: ^2.1.1
|
shared_preferences: ^2.1.1
|
||||||
sqflite: ^2.2.8+2
|
sqflite: ^2.2.8+2
|
||||||
|
dio: ^5.1.2
|
||||||
|
webview_flutter: ^4.2.0
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
|
|||||||
Reference in New Issue
Block a user