Explore page

This commit is contained in:
2024-03-23 23:05:04 +08:00
parent 8bb9c960c1
commit a3b4706ca2
15 changed files with 540 additions and 284 deletions

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:solaragent/auth.dart';
import 'package:solaragent/screens/about.dart';
import 'package:solaragent/widgets/name_card.dart';
@ -27,72 +26,69 @@ class _AccountScreenState extends State<AccountScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: Column(children: [
Padding(
padding: const EdgeInsets.only(top: 20),
child: NameCard(
onLogin: () async {
await authClient.signin(context);
var authorized = await authClient.isAuthorized();
setState(() {
isAuthorized = authorized;
});
},
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: Column(children: [
Padding(
padding: const EdgeInsets.only(top: 20),
child: NameCard(
onLogin: () async {
await authClient.signin(context);
var authorized = await authClient.isAuthorized();
setState(() {
isAuthorized = authorized;
});
},
),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Wrap(
spacing: 5,
children: [
FutureBuilder(
future: authClient.isAuthorized(),
builder:
(BuildContext context, AsyncSnapshot<bool> snapshot) {
return (snapshot.hasData && snapshot.data == true)
? InkWell(
borderRadius:
const BorderRadius.all(Radius.circular(40)),
splashColor: Colors.indigo.withAlpha(30),
onTap: () async {
authClient.signoff();
var authorized =
await authClient.isAuthorized();
setState(() {
isAuthorized = authorized;
});
},
child: const ListTile(
leading: Icon(Icons.logout),
title: Text('Logout'),
),
)
: Container();
},
),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Wrap(
spacing: 5,
children: [
FutureBuilder(
future: authClient.isAuthorized(),
builder:
(BuildContext context, AsyncSnapshot<bool> snapshot) {
return (snapshot.hasData && snapshot.data == true)
? InkWell(
borderRadius:
const BorderRadius.all(Radius.circular(40)),
splashColor: Colors.indigo.withAlpha(30),
onTap: () async {
authClient.signoff();
var authorized = await authClient.isAuthorized();
setState(() {
isAuthorized = authorized;
});
},
child: const ListTile(
leading: Icon(Icons.logout),
title: Text('Logout'),
),
)
: Container();
},
),
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(40)),
splashColor: Colors.indigo.withAlpha(30),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AboutScreen(),
));
},
child: const ListTile(
leading: Icon(Icons.info_outline),
title: Text('About'),
),
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(40)),
splashColor: Colors.indigo.withAlpha(30),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AboutScreen(),
));
},
child: const ListTile(
leading: Icon(Icons.info_outline),
title: Text('About'),
),
),
],
),
),
],
),
]),
),
),
]),
),
);
}

View File

@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class ApplicationScreen extends StatelessWidget {
final Uri link;
final String title;
const ApplicationScreen({super.key, required this.link, required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
bottom: false,
child: Stack(
children: [
WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (_) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Swipe from left to back to dashboard."),
));
}
))
..loadRequest(link)
..clearCache(),
),
],
),
),
);
}
}

View File

@ -1,93 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:solaragent/screens/application.dart';
import 'package:http/http.dart' as http;
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
final client = http.Client();
final directoryEndpoint =
Uri.parse('https://id.solsynth.dev/.well-known');
List<dynamic> directory = List.empty();
@override
void initState() {
super.initState();
_pullDirectory();
}
Future<void> _pullDirectory() async {
var response = await client.get(directoryEndpoint);
if (response.statusCode == 200) {
setState(() {
directory = jsonDecode(utf8.decode(response.bodyBytes))["directory"];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 20, right: 20, top: 30),
child: GridView.count(
crossAxisCount: 2,
children: directory.map((element) {
return Card(
child: InkWell(
onTap: () async {
var link = element["link"];
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
builder: (context) => ApplicationScreen(
link: Uri.parse(link),
title: element["name"],
),
));
},
splashColor: Colors.indigo.withAlpha(30),
child: Padding(
padding: const EdgeInsets.all(10),
child: Wrap(
spacing: 8.0,
children: [
Padding(
padding: const EdgeInsets.only(left: 6, top: 2),
child: Card(
shape: const CircleBorder(),
elevation: 0,
color: Colors.indigo.withAlpha(70),
child: const Padding(
padding: EdgeInsets.all(16),
child: Icon(Icons.apps),
),
),
),
ListTile(
title: Text(element['name']),
subtitle: Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(element['description'], style: Theme.of(context).textTheme.bodySmall),
),
),
],
),
),
),
);
}).toList(),
),
),
),
);
}
}

78
lib/screens/explore.dart Normal file
View File

@ -0,0 +1,78 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solaragent/models/feed.dart';
import 'package:solaragent/models/pagination.dart';
import 'package:http/http.dart' as http;
import 'package:solaragent/widgets/feed.dart';
class ExploreScreen extends StatefulWidget {
const ExploreScreen({super.key});
@override
State<ExploreScreen> createState() => _ExploreScreenState();
}
class _ExploreScreenState extends State<ExploreScreen> {
static const pageSize = 5;
final client = http.Client();
final PagingController<int, Feed> paginationController =
PagingController(firstPageKey: 0);
List<Feed> feed = List.empty();
@override
void initState() {
super.initState();
paginationController.addPageRequestListener((pageKey) {
pullFeed(pageKey);
});
}
Future<void> pullFeed(int pageKey) async {
var offset = pageKey;
var take = pageSize;
var uri =
Uri.parse('https://co.solsynth.dev/api/feed?take=$take&offset=$offset');
var res = await client.get(uri);
if (res.statusCode == 200) {
final result =
PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
final isLastPage = (result.data?.length ?? 0) < pageSize;
if (isLastPage) {
paginationController.appendLastPage(feed);
} else {
final feed = result.data!.map((x) => Feed.fromJson(x)).toList();
final nextPageKey = pageKey + feed.length;
paginationController.appendPage(feed, nextPageKey);
}
} else {
paginationController.error = utf8.decode(res.bodyBytes);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: PagedListView<int, Feed>(
pagingController: paginationController,
builderDelegate: PagedChildBuilderDelegate<Feed>(
itemBuilder: (context, item, index) => FeedItem(
item: item,
),
),
),
);
}
@override
void dispose() {
paginationController.dispose();
super.dispose();
}
}

View File

@ -46,94 +46,92 @@ class _NotificationScreenState extends State<NotificationScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: RefreshIndicator(
onRefresh: pullNotifications,
child: CustomScrollView(
slivers: [
// Title
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10, top: 20),
child: ListTile(
title: Text(
'Notifications',
style: Theme.of(context).textTheme.headlineSmall,
),
body: RefreshIndicator(
onRefresh: pullNotifications,
child: CustomScrollView(
slivers: [
// Title
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10, top: 20),
child: ListTile(
title: Text(
'Notifications',
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
// Content
notifications.isEmpty
? SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
color: Colors.grey[300],
child: const ListTile(
leading: Icon(Icons.check),
title: Text('You\'re done!'),
subtitle: Text(
'There are no notifications unread for you.',
),
),
// Content
notifications.isEmpty
? SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
color: Colors.grey[300],
child: const ListTile(
leading: Icon(Icons.check),
title: Text('You\'re done!'),
subtitle: Text(
'There are no notifications unread for you.',
),
),
)
: SliverList.builder(
itemCount: notifications.length,
itemBuilder: (BuildContext context, int index) {
var element = notifications[index];
return Dismissible(
key: Key('notification-$index'),
onDismissed: (direction) {
var subject = element["subject"];
markAsRead(element).then((value) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: RichText(
text: TextSpan(children: [
TextSpan(
text: subject,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
const TextSpan(
text: " is marked as read",
)
]),
),
),
);
});
setState(() {
notifications.removeAt(index);
});
},
background: Container(
color: Colors.green,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ListTile(
title: Text(element["subject"]),
subtitle: Text(element["content"]),
),
),
);
},
),
// Tips
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 10),
child: Text(
"Pull to refresh, swipe to dismiss",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall,
)
: SliverList.builder(
itemCount: notifications.length,
itemBuilder: (BuildContext context, int index) {
var element = notifications[index];
return Dismissible(
key: Key('notification-$index'),
onDismissed: (direction) {
var subject = element["subject"];
markAsRead(element).then((value) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: RichText(
text: TextSpan(children: [
TextSpan(
text: subject,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
const TextSpan(
text: " is marked as read",
)
]),
),
),
);
});
setState(() {
notifications.removeAt(index);
});
},
background: Container(
color: Colors.green,
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ListTile(
title: Text(element["subject"]),
subtitle: Text(element["content"]),
),
),
);
},
),
// Tips
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.only(top: 12),
child: Text(
"Pull to refresh, swipe to dismiss",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
),
],
),
),
);