channel and message lists
This commit is contained in:
parent
1286a5d848
commit
56d49d8c5e
@ -23,6 +23,7 @@ linter:
|
|||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
prefer_relative_imports: true,
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:simplecloudnotifier/models/key_token_auth.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.dart';
|
import 'package:simplecloudnotifier/models/user.dart';
|
||||||
|
|
||||||
|
import '../models/channel.dart';
|
||||||
|
import '../models/message.dart';
|
||||||
|
|
||||||
|
enum ChannelSelector {
|
||||||
|
owned(apiKey: 'owned'), // Return all channels of the user
|
||||||
|
subscribedAny(apiKey: 'subscribed_any'), // Return all channels that the user is subscribing to
|
||||||
|
allAny(apiKey: 'all_any'), // Return channels that the user owns or is subscribing
|
||||||
|
subscribed(apiKey: 'subscribed'), // Return all channels that the user is subscribing to (even unconfirmed)
|
||||||
|
all(apiKey: 'all'); // Return channels that the user owns or is subscribing (even unconfirmed)
|
||||||
|
|
||||||
|
const ChannelSelector({required this.apiKey});
|
||||||
|
final String apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
class APIClient {
|
class APIClient {
|
||||||
static const String _base = 'https://simplecloudnotifier.de/api/v2';
|
static const String _base = 'https://simplecloudnotifier.de/api/v2';
|
||||||
|
|
||||||
@ -23,4 +38,41 @@ class APIClient {
|
|||||||
|
|
||||||
return User.fromJson(jsonDecode(response.body));
|
return User.fromJson(jsonDecode(response.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getChannelList(KeyTokenAuth auth, ChannelSelector sel) async {
|
||||||
|
var url = '$_base/users/${auth.userId}/channels?selector=${sel.apiKey}';
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
|
||||||
|
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'});
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('API request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
|
return data['channels'].map<ChannelWithSubscription>((e) => ChannelWithSubscription.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMessageList(KeyTokenAuth auth, String pageToken, int? pageSize) async {
|
||||||
|
var url = '$_base/messages?next_page_token=$pageToken';
|
||||||
|
if (pageSize != null) {
|
||||||
|
url += '&page_size=$pageSize';
|
||||||
|
}
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
|
||||||
|
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'});
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('API request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
|
final npt = data['next_page_token'] as String;
|
||||||
|
|
||||||
|
final messages = data['messages'].map<Message>((e) => Message.fromJson(e)).toList();
|
||||||
|
|
||||||
|
return [npt, messages];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ import 'package:flutter/material.dart';
|
|||||||
class FabWithIcons extends StatefulWidget {
|
class FabWithIcons extends StatefulWidget {
|
||||||
FabWithIcons({super.key, required this.icons, required this.onIconTapped});
|
FabWithIcons({super.key, required this.icons, required this.onIconTapped});
|
||||||
final List<IconData> icons;
|
final List<IconData> icons;
|
||||||
ValueChanged<int> onIconTapped;
|
final ValueChanged<int> onIconTapped;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => FabWithIconsState();
|
State createState() => FabWithIconsState();
|
||||||
}
|
}
|
||||||
|
100
flutter/lib/models/channel.dart
Normal file
100
flutter/lib/models/channel.dart
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
|
|
||||||
|
class Channel {
|
||||||
|
final String channelID;
|
||||||
|
final String ownerUserID;
|
||||||
|
final String internalName;
|
||||||
|
final String displayName;
|
||||||
|
final String? descriptionName;
|
||||||
|
final String? subscribeKey;
|
||||||
|
final String timestampCreated;
|
||||||
|
final String? timestampLastSent;
|
||||||
|
final int messagesSent;
|
||||||
|
|
||||||
|
const Channel({
|
||||||
|
required this.channelID,
|
||||||
|
required this.ownerUserID,
|
||||||
|
required this.internalName,
|
||||||
|
required this.displayName,
|
||||||
|
required this.descriptionName,
|
||||||
|
required this.subscribeKey,
|
||||||
|
required this.timestampCreated,
|
||||||
|
required this.timestampLastSent,
|
||||||
|
required this.messagesSent,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Channel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return switch (json) {
|
||||||
|
{
|
||||||
|
'channel_id': String channelID,
|
||||||
|
'owner_user_id': String ownerUserID,
|
||||||
|
'internal_name': String internalName,
|
||||||
|
'display_name': String displayName,
|
||||||
|
'description_name': String? descriptionName,
|
||||||
|
'subscribe_key': String? subscribeKey,
|
||||||
|
'timestamp_created': String timestampCreated,
|
||||||
|
'timestamp_lastsent': String? timestampLastSent,
|
||||||
|
'messages_sent': int messagesSent,
|
||||||
|
} =>
|
||||||
|
Channel(
|
||||||
|
channelID: channelID,
|
||||||
|
ownerUserID: ownerUserID,
|
||||||
|
internalName: internalName,
|
||||||
|
displayName: displayName,
|
||||||
|
descriptionName: descriptionName,
|
||||||
|
subscribeKey: subscribeKey,
|
||||||
|
timestampCreated: timestampCreated,
|
||||||
|
timestampLastSent: timestampLastSent,
|
||||||
|
messagesSent: messagesSent,
|
||||||
|
),
|
||||||
|
_ => throw const FormatException('Failed to decode Channel.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChannelWithSubscription extends Channel {
|
||||||
|
final Subscription subscription;
|
||||||
|
|
||||||
|
ChannelWithSubscription({
|
||||||
|
required super.channelID,
|
||||||
|
required super.ownerUserID,
|
||||||
|
required super.internalName,
|
||||||
|
required super.displayName,
|
||||||
|
required super.descriptionName,
|
||||||
|
required super.subscribeKey,
|
||||||
|
required super.timestampCreated,
|
||||||
|
required super.timestampLastSent,
|
||||||
|
required super.messagesSent,
|
||||||
|
required this.subscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ChannelWithSubscription.fromJson(Map<String, dynamic> json) {
|
||||||
|
return switch (json) {
|
||||||
|
{
|
||||||
|
'channel_id': String channelID,
|
||||||
|
'owner_user_id': String ownerUserID,
|
||||||
|
'internal_name': String internalName,
|
||||||
|
'display_name': String displayName,
|
||||||
|
'description_name': String? descriptionName,
|
||||||
|
'subscribe_key': String? subscribeKey,
|
||||||
|
'timestamp_created': String timestampCreated,
|
||||||
|
'timestamp_lastsent': String? timestampLastSent,
|
||||||
|
'messages_sent': int messagesSent,
|
||||||
|
'subscription': dynamic subscription,
|
||||||
|
} =>
|
||||||
|
ChannelWithSubscription(
|
||||||
|
channelID: channelID,
|
||||||
|
ownerUserID: ownerUserID,
|
||||||
|
internalName: internalName,
|
||||||
|
displayName: displayName,
|
||||||
|
descriptionName: descriptionName,
|
||||||
|
subscribeKey: subscribeKey,
|
||||||
|
timestampCreated: timestampCreated,
|
||||||
|
timestampLastSent: timestampLastSent,
|
||||||
|
messagesSent: messagesSent,
|
||||||
|
subscription: Subscription.fromJson(subscription),
|
||||||
|
),
|
||||||
|
_ => throw const FormatException('Failed to decode Channel.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
67
flutter/lib/models/message.dart
Normal file
67
flutter/lib/models/message.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
class Message {
|
||||||
|
final String messageID;
|
||||||
|
final String senderUserID;
|
||||||
|
final String channelInternalName;
|
||||||
|
final String channelID;
|
||||||
|
final String? senderName;
|
||||||
|
final String senderIP;
|
||||||
|
final String timestamp;
|
||||||
|
final String title;
|
||||||
|
final String? content;
|
||||||
|
final int priority;
|
||||||
|
final String? userMessageID;
|
||||||
|
final String usedKeyID;
|
||||||
|
final bool trimmed;
|
||||||
|
|
||||||
|
const Message({
|
||||||
|
required this.messageID,
|
||||||
|
required this.senderUserID,
|
||||||
|
required this.channelInternalName,
|
||||||
|
required this.channelID,
|
||||||
|
required this.senderName,
|
||||||
|
required this.senderIP,
|
||||||
|
required this.timestamp,
|
||||||
|
required this.title,
|
||||||
|
required this.content,
|
||||||
|
required this.priority,
|
||||||
|
required this.userMessageID,
|
||||||
|
required this.usedKeyID,
|
||||||
|
required this.trimmed,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Message.fromJson(Map<String, dynamic> json) {
|
||||||
|
return switch (json) {
|
||||||
|
{
|
||||||
|
'message_id': String messageID,
|
||||||
|
'sender_user_id': String senderUserID,
|
||||||
|
'channel_internal_name': String channelInternalName,
|
||||||
|
'channel_id': String channelID,
|
||||||
|
'sender_name': String? senderName,
|
||||||
|
'sender_ip': String senderIP,
|
||||||
|
'timestamp': String timestamp,
|
||||||
|
'title': String title,
|
||||||
|
'content': String? content,
|
||||||
|
'priority': int priority,
|
||||||
|
'usr_message_id': String? userMessageID,
|
||||||
|
'used_key_id': String usedKeyID,
|
||||||
|
'trimmed': bool trimmed,
|
||||||
|
} =>
|
||||||
|
Message(
|
||||||
|
messageID: messageID,
|
||||||
|
senderUserID: senderUserID,
|
||||||
|
channelInternalName: channelInternalName,
|
||||||
|
channelID: channelID,
|
||||||
|
senderName: senderName,
|
||||||
|
senderIP: senderIP,
|
||||||
|
timestamp: timestamp,
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
priority: priority,
|
||||||
|
userMessageID: userMessageID,
|
||||||
|
usedKeyID: usedKeyID,
|
||||||
|
trimmed: trimmed,
|
||||||
|
),
|
||||||
|
_ => throw const FormatException('Failed to decode Message.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
43
flutter/lib/models/subscription.dart
Normal file
43
flutter/lib/models/subscription.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
class Subscription {
|
||||||
|
final String subscriptionID;
|
||||||
|
final String subscriberUserID;
|
||||||
|
final String channelOwnerUserID;
|
||||||
|
final String channelID;
|
||||||
|
final String channelInternalName;
|
||||||
|
final String timestampCreated;
|
||||||
|
final bool confirmed;
|
||||||
|
|
||||||
|
const Subscription({
|
||||||
|
required this.subscriptionID,
|
||||||
|
required this.subscriberUserID,
|
||||||
|
required this.channelOwnerUserID,
|
||||||
|
required this.channelID,
|
||||||
|
required this.channelInternalName,
|
||||||
|
required this.timestampCreated,
|
||||||
|
required this.confirmed,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Subscription.fromJson(Map<String, dynamic> json) {
|
||||||
|
return switch (json) {
|
||||||
|
{
|
||||||
|
'subscription_id': String subscriptionID,
|
||||||
|
'subscriber_user_id': String subscriberUserID,
|
||||||
|
'channel_owner_user_id': String channelOwnerUserID,
|
||||||
|
'channel_id': String channelID,
|
||||||
|
'channel_internal_name': String channelInternalName,
|
||||||
|
'timestamp_created': String timestampCreated,
|
||||||
|
'confirmed': bool confirmed,
|
||||||
|
} =>
|
||||||
|
Subscription(
|
||||||
|
subscriptionID: subscriptionID,
|
||||||
|
subscriberUserID: subscriberUserID,
|
||||||
|
channelOwnerUserID: channelOwnerUserID,
|
||||||
|
channelID: channelID,
|
||||||
|
channelInternalName: channelInternalName,
|
||||||
|
timestampCreated: timestampCreated,
|
||||||
|
confirmed: confirmed,
|
||||||
|
),
|
||||||
|
_ => throw const FormatException('Failed to decode Subscription.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/channel_list/root.dart';
|
||||||
import 'package:simplecloudnotifier/pages/send/root.dart';
|
import 'package:simplecloudnotifier/pages/send/root.dart';
|
||||||
|
|
||||||
import 'bottom_fab/fab_bottom_app_bar.dart';
|
import 'bottom_fab/fab_bottom_app_bar.dart';
|
||||||
import 'pages/account/root.dart';
|
import 'pages/account/root.dart';
|
||||||
import 'pages/message_list/message_list.dart';
|
import 'pages/message_list/message_list.dart';
|
||||||
|
import 'pages/settings/root.dart';
|
||||||
import 'state/app_theme.dart';
|
import 'state/app_theme.dart';
|
||||||
|
|
||||||
class SCNNavLayout extends StatefulWidget {
|
class SCNNavLayout extends StatefulWidget {
|
||||||
@ -19,10 +21,10 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
int _selectedIndex = 0; // 4 == FAB
|
int _selectedIndex = 0; // 4 == FAB
|
||||||
|
|
||||||
static const List<Widget> _subPages = <Widget>[
|
static const List<Widget> _subPages = <Widget>[
|
||||||
MessageListPage(title: 'Messages'),
|
MessageListPage(),
|
||||||
MessageListPage(title: 'Page 2'),
|
ChannelRootPage(),
|
||||||
AccountRootPage(),
|
AccountRootPage(),
|
||||||
MessageListPage(title: 'Page 4'),
|
SettingsRootPage(),
|
||||||
SendRootPage(),
|
SendRootPage(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -33,12 +33,13 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
FractionallySizedBox(
|
||||||
width: 250,
|
widthFactor: 1.0,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _ctrlUserID,
|
controller: _ctrlUserID,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@ -48,8 +49,8 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SizedBox(
|
FractionallySizedBox(
|
||||||
width: 250,
|
widthFactor: 1.0,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _ctrlToken,
|
controller: _ctrlToken,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
18
flutter/lib/pages/channel_list/channel_list_item.dart
Normal file
18
flutter/lib/pages/channel_list/channel_list_item.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../models/channel.dart';
|
||||||
|
|
||||||
|
class ChannelListItem extends StatelessWidget {
|
||||||
|
const ChannelListItem({
|
||||||
|
required this.channel,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Channel channel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => ListTile(
|
||||||
|
leading: const SizedBox(width: 40, height: 40, child: const Placeholder()),
|
||||||
|
title: Text(channel.internalName),
|
||||||
|
);
|
||||||
|
}
|
64
flutter/lib/pages/channel_list/root.dart
Normal file
64
flutter/lib/pages/channel_list/root.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
|
|
||||||
|
import '../../state/user_account.dart';
|
||||||
|
import 'channel_list_item.dart';
|
||||||
|
|
||||||
|
class ChannelRootPage extends StatefulWidget {
|
||||||
|
const ChannelRootPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChannelRootPage> createState() => _ChannelRootPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelRootPageState extends State<ChannelRootPage> {
|
||||||
|
final PagingController<int, Channel> _pagingController = PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
|
late UserAccount userAcc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_pagingController.addPageRequestListener((pageKey) {
|
||||||
|
_fetchPage(pageKey);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pagingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchPage(int pageKey) async {
|
||||||
|
final acc = Provider.of<UserAccount>(context, listen: false);
|
||||||
|
|
||||||
|
if (acc.auth == null) {
|
||||||
|
_pagingController.error = 'Not logged in';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final items = await APIClient.getChannelList(acc.auth!, ChannelSelector.all);
|
||||||
|
|
||||||
|
_pagingController.appendLastPage(items);
|
||||||
|
} catch (error) {
|
||||||
|
_pagingController.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PagedListView<int, Channel>(
|
||||||
|
pagingController: _pagingController,
|
||||||
|
builderDelegate: PagedChildBuilderDelegate<Channel>(
|
||||||
|
itemBuilder: (context, item, index) => ChannelListItem(
|
||||||
|
channel: item,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,72 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
|
||||||
class MessageListPage extends StatelessWidget {
|
import '../../models/message.dart';
|
||||||
final String title;
|
import '../../state/user_account.dart';
|
||||||
|
import 'message_list_item.dart';
|
||||||
|
|
||||||
const MessageListPage({super.key, required this.title});
|
class MessageListPage extends StatefulWidget {
|
||||||
|
const MessageListPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageListPage> createState() => _MessageListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageListPageState extends State<MessageListPage> {
|
||||||
|
static const _pageSize = 20; //TODO
|
||||||
|
|
||||||
|
final PagingController<String, Message> _pagingController = PagingController(firstPageKey: '@start');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_pagingController.addPageRequestListener((pageKey) {
|
||||||
|
_fetchPage(pageKey);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pagingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchPage(String thisPageToken) async {
|
||||||
|
final acc = Provider.of<UserAccount>(context, listen: false);
|
||||||
|
|
||||||
|
if (acc.auth == null) {
|
||||||
|
_pagingController.error = 'Not logged in';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final [npt, newItems] = await APIClient.getMessageList(acc.auth!, thisPageToken, _pageSize);
|
||||||
|
|
||||||
|
if (npt == '@end') {
|
||||||
|
_pagingController.appendLastPage(newItems);
|
||||||
|
} else {
|
||||||
|
_pagingController.appendPage(newItems, npt);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
_pagingController.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return PagedListView<String, Message>(
|
||||||
body: Center(
|
pagingController: _pagingController,
|
||||||
child: Text(
|
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||||
title,
|
itemBuilder: (context, item, index) => MessageListItem(
|
||||||
style: const TextStyle(fontSize: 24),
|
message: item,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _createChannel() {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
flutter/lib/pages/message_list/message_list_item.dart
Normal file
17
flutter/lib/pages/message_list/message_list_item.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
|
|
||||||
|
class MessageListItem extends StatelessWidget {
|
||||||
|
const MessageListItem({
|
||||||
|
required this.message,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Message message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => ListTile(
|
||||||
|
leading: const SizedBox(width: 40, height: 40, child: const Placeholder()),
|
||||||
|
title: Text(message.messageID),
|
||||||
|
);
|
||||||
|
}
|
17
flutter/lib/pages/settings/root.dart
Normal file
17
flutter/lib/pages/settings/root.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SettingsRootPage extends StatefulWidget {
|
||||||
|
const SettingsRootPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsRootPage> createState() => _SettingsRootPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsRootPageState extends State<SettingsRootPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Text('Settings'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -82,10 +82,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
|
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "3.0.1"
|
||||||
|
flutter_staggered_grid_view:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_staggered_grid_view
|
||||||
|
sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -119,14 +127,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
infinite_scroll_pagination:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: infinite_scroll_pagination
|
||||||
|
sha256: b68bce20752fcf36c7739e60de4175494f74e99e9a69b4dd2fe3a1dd07a7f16a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "3.0.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -292,6 +308,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
sliver_tools:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sliver_tools
|
||||||
|
sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.12"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -41,6 +41,7 @@ dependencies:
|
|||||||
shared_preferences: ^2.2.2
|
shared_preferences: ^2.2.2
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
url_launcher: ^6.2.4
|
url_launcher: ^6.2.4
|
||||||
|
infinite_scroll_pagination: ^4.0.0
|
||||||
|
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
@ -57,7 +58,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^3.0.1
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
Loading…
Reference in New Issue
Block a user