diff --git a/lib/app_page.dart b/lib/app_page.dart index 5ef4190..c1aa97b 100644 --- a/lib/app_page.dart +++ b/lib/app_page.dart @@ -18,146 +18,163 @@ class _AppPageState extends State { super.initState(); _loadAppInfo(); } + void _loadAppInfo() async { - var rq = await http.get( - Uri.https( - "backapi.rustore.ru", - "/applicationData/overallInfo/${widget.packageName}", - ) - ); + var rq = await http.get(Uri.https( + "backapi.rustore.ru", + "/applicationData/overallInfo/${widget.packageName}", + )); + print(widget.packageName + "PN"); if (rq.statusCode != 200) { showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Что то пошло не так"), - content: Text("RuStore вернул код ${rq.statusCode}"), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context) - ..pop() - ..pop(); - }, - child: const Text("ок"), - ) - ], - ) - ); + context: context, + builder: (context) => AlertDialog( + title: const Text("Что то пошло не так"), + content: Text("RuStore вернул код ${rq.statusCode}"), + actions: [ + FilledButton( + onPressed: () { + Navigator.of(context) + ..pop() + ..pop(); + }, + child: const Text("ок"), + ) + ], + )); } setState(() { - _appInfo = json.decode(utf8.decode(rq.bodyBytes))["body"]; - }); + _appInfo = json.decode(utf8.decode(rq.bodyBytes))["body"] ?? {}; + }); } + @override Widget build(BuildContext context) { - return _appInfo != null ? ListView( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 15), - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(25), - child: Image.network( - _appInfo?["iconUrl"], - width: 120, - ), - ), - const SizedBox(width: 15,), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _appInfo?["appName"], - //overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 21, - fontWeight: FontWeight.bold, + return Scaffold( + appBar: AppBar(), + body: _appInfo != null + ? ListView( + padding: const EdgeInsets.fromLTRB(15, 0, 15, 15), + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(25), + child: Image.network( + _appInfo?["iconUrl"] ?? "", + width: 120, ), ), - const SizedBox(height: 10,), - Text( - _appInfo?["companyName"], - overflow: TextOverflow.ellipsis, - softWrap: false, + const SizedBox( + width: 15, ), - ], - ) - ), - ], - ), - const SizedBox(height: 20,), - FilledButton.icon( - style: FilledButton.styleFrom( - minimumSize: const Size.fromHeight(40), - ), - onPressed: () async { - launchUrl(await getAppLink(_appInfo?["appId"], context)); - }, - label: const Text("Скачать APK"), - ), - const SizedBox(height: 15), - SizedBox( - height: 84, - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - _InfoCard( - topText: "${(_appInfo?["fileSize"]/1024/1024).toStringAsFixed(1)} MB", - bottomText: "Размер", - ), - _InfoCard( - topText: _appInfo?["versionName"], - bottomText: "Версия", - ), - _InfoCard( - topText: _appInfo?["minSdkVersion"].toString() ?? "", - bottomText: "minSdkVersion", - ), - _InfoCard( - topText: _appInfo?["ageRestriction"]["category"] ?? "?", - bottomText: "Возраст", - ), - _InfoCard( - topText: _appInfo?["categories"][0] ?? "?", - bottomText: "Категория", - ) - ], - ), - ), - const SizedBox(height: 15), - SizedBox( - height: 250, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: _appInfo?["fileUrls"].length, - itemBuilder: (context, i) { - var file = _appInfo?["fileUrls"][i]; - if (file["type"] != "SCREENSHOT" && file["orientation"] != "PORTRAIT") { - return const SizedBox(); - } - //return Text("data${i}"); - return Padding( - padding: const EdgeInsets.all(5), - child: ClipRRect( - borderRadius: BorderRadius.circular(15), - child: Image.network( - file["fileUrl"], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _appInfo?["appName"] ?? "null", + //overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 21, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 10, + ), + Text( + _appInfo?["companyName"] ?? "null", + overflow: TextOverflow.ellipsis, + softWrap: false, + ), + ], + )), + ], + ), + const SizedBox( + height: 20, + ), + FilledButton.icon( + style: FilledButton.styleFrom( + minimumSize: const Size.fromHeight(40), + ), + onPressed: () async { + launchUrl(await getAppLink(_appInfo?["appId"], context)); + }, + label: const Text("Скачать APK"), + ), + const SizedBox(height: 15), + SizedBox( + height: 84, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + _InfoCard( + topText: + "${((_appInfo?["fileSize"] ?? 0) / 1024 / 1024).toStringAsFixed(1)} MB", + bottomText: "Размер", + ), + _InfoCard( + topText: _appInfo?["versionName"] ?? "0", + bottomText: "Версия", + ), + _InfoCard( + topText: _appInfo?["minSdkVersion"].toString() ?? "", + bottomText: "minSdkVersion", + ), + _InfoCard( + topText: + _appInfo?["ageRestriction"]?["category"] ?? "?", + bottomText: "Возраст", + ), + _InfoCard( + topText: _appInfo?["categories"]?[0] ?? "?", + bottomText: "Категория", + ) + ], ), ), - ); - } - ), - ), - const SizedBox(height: 15), - const Text("Что нового?", style: TextStyle(fontSize: 21, fontWeight: FontWeight.bold),), - Text(_appInfo?["whatsNew"]), - const SizedBox(height: 15), - const Text("Описание", style: TextStyle(fontSize: 21, fontWeight: FontWeight.bold),), - Text(_appInfo?["fullDescription"]), - ], - ) : const LinearProgressIndicator(); + const SizedBox(height: 15), + SizedBox( + height: 250, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: _appInfo?["fileUrls"]?.length ?? 0, + itemBuilder: (context, i) { + var file = _appInfo?["fileUrls"][i]; + if (file["type"] != "SCREENSHOT" && + file["orientation"] != "PORTRAIT") { + return const SizedBox(); + } + //return Text("data${i}"); + return Padding( + padding: const EdgeInsets.all(5), + child: ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Image.network( + file["fileUrl"], + ), + ), + ); + }), + ), + const SizedBox(height: 15), + const Text( + "Что нового?", + style: TextStyle(fontSize: 21, fontWeight: FontWeight.bold), + ), + Text(_appInfo?["whatsNew"] ?? "null"), + const SizedBox(height: 15), + const Text( + "Описание", + style: TextStyle(fontSize: 21, fontWeight: FontWeight.bold), + ), + Text(_appInfo?["fullDescription"] ?? "null"), + ], + ) + : const LinearProgressIndicator()); } } @@ -183,5 +200,4 @@ class _InfoCard extends StatelessWidget { ), ); } - -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 2019365..f6c2df1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,17 +1,44 @@ import 'package:flutter/material.dart'; +import 'package:openstore/app_page.dart'; import 'package:openstore/search_page.dart'; +import 'package:go_router/go_router.dart'; void main() { runApp(const MyApp()); } +final _router = GoRouter( + routes: [ + GoRoute(path: '/', builder: (context, state) => const HomePage(), routes: [ + GoRoute( + path: '/app/:id', + builder: (context, state) => + AppPage(packageName: state.pathParameters['id'] ?? ""), + ), + GoRoute( + path: 'instruction', + builder: (context, state) { + print(state.uri.queryParameters["utm_campaign"]); + return AppPage(packageName: "ru.nspk.mirpay"); + }) + ]), + GoRoute( + path: '/search', + builder: (context, state) => + SearchPage(search: state.uri.queryParameters["q"] ?? "")), + GoRoute( + path: '/catalog/app/:id', + redirect: (_, state) => "/app/${state.pathParameters["id"]}"), + ], +); + class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( + return MaterialApp.router( title: 'OpenStore', theme: ThemeData( colorScheme: ColorScheme.fromSeed( @@ -20,7 +47,7 @@ class MyApp extends StatelessWidget { ), useMaterial3: true, ), - home: const HomePage(), + routerConfig: _router, ); } } @@ -37,38 +64,28 @@ class HomePage extends StatelessWidget { body: Center( child: Row( children: [ - const SizedBox(width: 15,), + const SizedBox( + width: 15, + ), Flexible( child: TextField( - onSubmitted: (value) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return Scaffold( - appBar: AppBar( - title: Text("Поиск: "+value), - - ), - body: SearchPage(search: value,), - ); - } - ) - ); - }, + onSubmitted: (value) => context.push( + Uri(path: '/search', queryParameters: {'q': value}) + .toString()), textInputAction: TextInputAction.search, decoration: InputDecoration( - prefixIcon: Icon(Icons.search), - border: OutlineInputBorder() - ), + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder()), ), ), //const SizedBox(width: 5,), //FilledButton(onPressed: (){}, child: Text("GO")), - const SizedBox(width: 15,), + const SizedBox( + width: 15, + ), ], ), ), ); } - -} \ No newline at end of file +} diff --git a/lib/search_page.dart b/lib/search_page.dart index 5f8795f..4f17313 100644 --- a/lib/search_page.dart +++ b/lib/search_page.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:openstore/app_page.dart'; import 'package:openstore/get_app_link.dart'; @@ -21,107 +22,96 @@ class _SearchPageState extends State { super.initState(); _loadResults(); } + void _loadResults() async { - var rq = await http.get( - Uri.https( - "backapi.rustore.ru", - "/applicationData/apps", - { - "pageSize": "20", - "query": widget.search, - } - ) - ); + var rq = await http + .get(Uri.https("backapi.rustore.ru", "/applicationData/apps", { + "pageSize": "20", + "query": widget.search, + })); if (rq.statusCode != 200) { showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Что то пошло не так"), - content: Text("RuStore вернул код ${rq.statusCode}"), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context) - ..pop() - ..pop(); - }, - child: const Text("ок"), - ) - ], - ) - ); + context: context, + builder: (context) => AlertDialog( + title: const Text("Что то пошло не так"), + content: Text("RuStore вернул код ${rq.statusCode}"), + actions: [ + FilledButton( + onPressed: () { + Navigator.of(context) + ..pop() + ..pop(); + }, + child: const Text("ок"), + ) + ], + )); } setState(() { - _searchResults = json.decode(utf8.decode(rq.bodyBytes))["body"]["content"]; + _searchResults = + json.decode(utf8.decode(rq.bodyBytes))["body"]["content"]; }); } + @override Widget build(BuildContext context) { - if (_searchResults == null) { - return const LinearProgressIndicator(); - } - return ListView.builder( - itemCount: _searchResults?.length, - padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), - itemBuilder: (context, i) { - var appInfo = _searchResults?[i]; - return Card( - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () async { - Navigator.of(context).push( - MaterialPageRoute(builder: (ctx) => - Scaffold( - appBar: AppBar(), - body: AppPage(packageName: appInfo["packageName"]), - ) - ) - ); - }, - child: Padding( - padding: const EdgeInsets.all(15), - child: Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(15), - child: Image.network( - width: 60, - appInfo["iconUrl"] - ), - ), - const SizedBox(width: 15,), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - appInfo["appName"], - style: const TextStyle( - fontWeight: FontWeight.bold, + return Scaffold( + appBar: AppBar(title: Text("Поиск")), + body: _searchResults == null + ? LinearProgressIndicator() + : ListView.builder( + itemCount: _searchResults?.length, + padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), + itemBuilder: (context, i) { + var appInfo = _searchResults?[i]; + return Card( + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () => + context.push("/app/${appInfo["packageName"]}"), + child: Padding( + padding: const EdgeInsets.all(15), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Image.network( + width: 60, appInfo["iconUrl"]), + ), + const SizedBox( + width: 15, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + appInfo["appName"], + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + appInfo["packageName"], + overflow: TextOverflow.ellipsis, + ) + ], + ), + ), + IconButton.filledTonal( + onPressed: () async { + Clipboard.setData(ClipboardData( + text: (await getAppLink( + appInfo?["appId"], context)) + .toString())); + }, + icon: const Icon(Icons.link), + ), + ], ), - overflow: TextOverflow.ellipsis, ), - Text( - appInfo["packageName"], - overflow: TextOverflow.ellipsis, - ) - ], - ), - ), - IconButton.filledTonal( - onPressed: ()async{ - Clipboard.setData( - ClipboardData(text: (await getAppLink(appInfo?["appId"], context)).toString()) - ); - }, - icon: const Icon(Icons.link), - ), - ], - ), - ), - ) - ); - } - ); + )); + })); } -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index d54c7c8..6f4acd1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -80,6 +80,14 @@ packages: description: flutter source: sdk version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "2b9ba6d4c247457c35a6622f1dee6aab6694a4e15237ff7c32320345044112b6" + url: "https://pub.dev" + source: hosted + version: "15.1.1" http: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 78b509b..7344fcd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 http: ^1.2.1 + go_router: ^15.1.1 dev_dependencies: flutter_test: