Friends who raise kittens come to my shop! Douyin mall search #早晚早得的猫小店子
The cat grocery store project is being continuously updated
If the APP design idea is changed to catpedia, it will not know the back end, the data source captures the packet, and saves it locally
- Log in and register, add personal information to local storage and modify
- Internationalized version
- day and night mode
- Cart item removal
- Order to order payment process
- Tools sorting (font size, font color, theme color, etc.)
- Image library tool processing
- webview page interception optimization
- Overall performance optimization
- Packaging
sp_util is a nice utility class
flutter_screenutil
Need to register width and height when using
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 681),
builder: (BuildContext context, Widget? child) => ChangeNotifierProvider(
create: (context) => CarModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const StartPage(),
),
),
);
}
}
webview_flutter
Add webview jump
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebPage extends StatefulWidget {
final String url;
final String title;
const WebPage({super.key, required this.url, required this.title});
@override
State<WebPage> createState() => _WebPageState(this.url, this.title);
}
class _WebPageState extends State<WebPage> {
final String url;
final String title;
late final WebViewController controller;
_WebPageState(this.url, this.title);
@override
void initState() {
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://pub.dev/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(url));
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Expanded(
child: WebViewWidget(controller: controller),
),
],
),
);
}
}
Countdown ads, use Timer to count down,
second_index is the set time
Set the time to modify by yourself, or you can modify it on the server side
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cat_1/pages/intro_page.dart';
import 'package:flutter_cat_1/tools/tools.dart';
import 'package:flutter_cat_1/web/web_page.dart';
import 'package:google_fonts/google_fonts.dart';
import '../main.dart';
class WelcomePage extends StatefulWidget {
const WelcomePage({super.key});
@override
State<WelcomePage> createState() => _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage> {
int second_index = 3;
int timeIndex = 0;
late Timer timer;
@override
void initState() {
super.initState();
timer = Timer.periodic(
Duration(milliseconds: 50),
(sd) {
setState(() {
timeIndex += 1;
if (timeIndex % 20 == 0) {
second_index -= 1;
}
});
if (timeIndex >= 100) {
_openMain();
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Image.asset(
"assets/IMG_8050.png",
fit: BoxFit.cover,
),
),
Container(
alignment: Alignment.bottomRight,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
),
alignment: Alignment.center,
margin: const EdgeInsets.all(25),
child: GestureDetector(
onTap: () {
_openMain();
},
child: Stack(
children: <Widget>[
Container(
alignment: Alignment.center,
child: CircularProgressIndicator(
value: timeIndex / 100,
strokeWidth: 2,
),
),
Container(
alignment: Alignment.center,
child: Text(
"跳过",
style: TextStyle(fontSize: 12,color: Colors.white,fontWeight: FontWeight.bold,),
),
),
],
),
),
),
),
Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RichText(
text: TextSpan(
text: "Flutter开发日常练习",
style: GoogleFonts.notoSerif(
color: Colors.black,
fontSize: 17,
),
children: [
TextSpan(
text: "-小猫咪杂货店",
style: GoogleFonts.notoSerif(
color: Colors.black,
fontSize: 17,
// decoration: TextDecoration.underline,
// decorationColor: Colors.blue[800],
)),
],
),
textDirection: TextDirection.ltr,
),
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
text: 'CSDN关注',
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.black,
),
children: [
TextSpan(
text: 'workersJiaDa',
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[800],
decoration: TextDecoration.underline,
decorationColor: Colors.blue[800],
fontWeight: FontWeight.bold,
),
recognizer: TapGestureRecognizer()
..onTap = () {
timer.cancel();
Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) => WebPage(
url:
"https://blog.csdn.net/zxc8890304",
title: "workersJiaDa的博客"),
),
)
.then((value) => reGetTimer());
}),
],
),
),
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
text: '抖音商城搜索',
style: GoogleFonts.notoSerif(
color: Colors.black,
fontSize: 17,
),
children: [
TextSpan(
text: "早睡早起的猫咪小铺子",
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[800],
decoration: TextDecoration.underline,
decorationColor: Colors.blue[800],
fontWeight: FontWeight.bold,
),
recognizer: TapGestureRecognizer()
..onTap = () {
// timer.cancel();
launchURL('snssdk1128://user/profile/88486395468');
},
),
],
),
),
],
),
),
],
),
);
}
void _openMain() {
timer.cancel();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const IntroPage(),
),
(route) => false);
}
void reGetTimer() {
timer = Timer.periodic(
Duration(milliseconds: 50),
(sd) {
setState(() {
timeIndex += 1;
if (timeIndex % 20 == 0) {
second_index -= 1;
}
});
if (timeIndex >= 100) {
_openMain();
}
},
);
}
}
The words on the welcome page are
flutter_swiper_plus
Very crude, simple to achieve the effect, to re-optimize and modify
The judgment of isFirst has been added, and it will be displayed only when the APP is opened for the first time.
I have done international projects before, and the overseas demand is
As long as you enter the welcome page without clicking skip or entering the APP , you will enter the welcome page again every time you enter the APP
In this case, the judgment conditions need to be revised.
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cat_1/welcome/welcome_page.dart';
import 'package:flutter_swiper_plus/flutter_swiper_plus.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:sp_util/sp_util.dart';
class StartPage extends StatefulWidget {
const StartPage({super.key});
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
bool _isLogin = false;
bool _isFirst = false;
bool _islastPage = false;
int pageInt = 1;
final List _welcomeList = ["welcome_1.png", "welcome_2.png", "welcome_3.png"];
@override
void initState() {
super.initState();
_isFirst = SpUtil.getBool("first")!;
if (_isFirst) {
String? tokenStr = SpUtil.getString('token');
_isLogin = (tokenStr == null || tokenStr.isEmpty) ? false : true;
new Future.delayed(Duration(seconds: 0), () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => WelcomePage(),
),
(route) => false);
});
}
}
@override
Widget build(BuildContext context) {
return
Container(
color: Colors.white,
height: ScreenUtil().screenHeight,
child: Stack(
children: [
Swiper(
itemBuilder: (context, index) {
return Image.asset(
"assets/${_welcomeList[index]}",
fit: BoxFit.cover,
);
},
itemCount: _welcomeList.length,
loop: false,
onIndexChanged: (index) {
pageInt = index + 1;
if (index == _welcomeList.length - 1) {
setState(() {
_islastPage = true;
});
} else {
setState(() {
_islastPage = false;
});
}
},
),
Container(
width: 100,
height: 40,
alignment: Alignment.center,
margin: EdgeInsets.only(
left: ScreenUtil().screenWidth / 2 - 50,
bottom: 40,
top: ScreenUtil().screenHeight - 80,
right: ScreenUtil().screenWidth / 2 - 50,
),
child: Text(
"$pageInt / 3",
style: const TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none),
),
),
Container(
width: 100,
height: 40,
margin: EdgeInsets.only(
left: ScreenUtil().screenWidth / 2 + 100,
bottom: 40,
top: ScreenUtil().screenHeight - 80,
// right: ScreenUtil().screenWidth / 2 - 10,
),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.blue[900], width: 2),
// borderRadius: BorderRadius.circular(8),
// ),
child: Center(
child: TextButton(
onPressed:() => _jumpWelcome(),
// Navigator.push(context, MaterialPageRoute(
// builder: (context) {
// },
// ));
child: _islastPage
? Text(
"立即进入",
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[700],
fontWeight: FontWeight.bold),
)
: Text(
"跳过",
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[700],
fontWeight: FontWeight.bold),
),
),
),
)
],
),
);
}
_jumpWelcome() {
SpUtil.putBool('first',true);
String? tokenStr = SpUtil.getString("token");
_isLogin = (tokenStr == null || tokenStr.isEmpty) ? false : true;
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => WelcomePage(),
),
(route) => false);
}
}
The side sliding bar is also, relatively simple
import 'package:flutter/material.dart';
import 'package:flutter_cat_1/welcome/start_page.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class DrawerPage extends StatelessWidget {
const DrawerPage({super.key});
@override
Widget build(BuildContext context) {
return Drawer(
elevation: 300,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("铲屎官铲屎官铲屎官"),
accountEmail: Text("饲养员饲养员饲养员"),
currentAccountPicture: CircleAvatar(
backgroundImage: AssetImage("assets/IMG_9406.png"),
),
arrowColor: Colors.red,
onDetailsPressed: () {},
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
offset: Offset(10, 10),
blurRadius: 45,
spreadRadius: 0.0,
),
],
image: DecorationImage(
image: AssetImage(
"assets/IMG_0259.png",
),
fit: BoxFit.cover),
),
),
Expanded(
child: ListView(
padding: EdgeInsets.symmetric(vertical: 10),
children: <Widget>[
ListTile(
title: Text('个人中心'),
),
Divider(),
ListTile(
title: Text('我的小猫咪'),
),
Divider(),
ListTile(
title: Text("我的订单"),
),
Divider(),
ListTile(
title: Text("关于我们"),
),
Divider(),
],
),
),
],
),
Positioned(
bottom: 20,
right: 20,
child: InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () => Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) {
return StartPage();
},
), (route) => false),
child: MenuItemButton(
child: Column(
children: const <Widget>[
Icon(
Icons.power_settings_new_outlined,
color: Colors.red,
),
Text(
"退出登录",
style: TextStyle(
color: Colors.red,
fontSize: 12,
),
),
],
)),
),
],
),
),
),
],
),
);
}
}
DEMO address