diff --git a/analysis_options.yaml b/analysis_options.yaml index c0e32f0..93361f5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,5 +7,5 @@ linter: analyzer: errors: todo: ignore -# exclude: -# - path/to/excluded/files/** \ No newline at end of file + exclude: + - example/main.dart \ No newline at end of file diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 0000000..b880ffc --- /dev/null +++ b/example/main.dart @@ -0,0 +1,264 @@ +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(FamedlySdkExampleApp()); +} + +class FamedlySdkExampleApp extends StatelessWidget { + static Client client = Client('Famedly SDK Example Client', debug: true); + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Famedly SDK Example App', + home: LoginView(), + ); + } +} + +class LoginView extends StatefulWidget { + @override + _LoginViewState createState() => _LoginViewState(); +} + +class _LoginViewState extends State { + final TextEditingController _homeserverController = TextEditingController(); + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + bool _isLoading = false; + String _error; + + void _loginAction() async { + setState(() => _isLoading = true); + setState(() => _error = null); + try { + if (await FamedlySdkExampleApp.client + .checkServer(_homeserverController.text) == + false) { + throw (Exception('Server not supported')); + } + if (await FamedlySdkExampleApp.client.login( + _usernameController.text, + _passwordController.text, + ) == + false) { + throw (Exception('Username or password incorrect')); + } + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (_) => ChatListView()), + (route) => false, + ); + } catch (e) { + setState(() => _error = e.toString()); + } + setState(() => _isLoading = false); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Login')), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + TextField( + controller: _homeserverController, + readOnly: _isLoading, + autocorrect: false, + decoration: InputDecoration( + labelText: 'Homeserver', + hintText: 'https://matrix.org', + ), + ), + SizedBox(height: 8), + TextField( + controller: _usernameController, + readOnly: _isLoading, + autocorrect: false, + decoration: InputDecoration( + labelText: 'Username', + hintText: '@username:domain', + ), + ), + SizedBox(height: 8), + TextField( + controller: _passwordController, + obscureText: true, + readOnly: _isLoading, + autocorrect: false, + decoration: InputDecoration( + labelText: 'Password', + hintText: '****', + errorText: _error, + ), + ), + SizedBox(height: 8), + RaisedButton( + child: _isLoading ? LinearProgressIndicator() : Text('Login'), + onPressed: _isLoading ? null : _loginAction, + ), + ], + ), + ); + } +} + +class ChatListView extends StatefulWidget { + @override + _ChatListViewState createState() => _ChatListViewState(); +} + +class _ChatListViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Chats'), + ), + body: StreamBuilder( + stream: FamedlySdkExampleApp.client.onSync.stream, + builder: (c, s) => ListView.builder( + itemCount: FamedlySdkExampleApp.client.rooms.length, + itemBuilder: (BuildContext context, int i) { + final room = FamedlySdkExampleApp.client.rooms[i]; + return ListTile( + title: Text(room.displayname + ' (${room.notificationCount})'), + subtitle: Text(room.lastMessage, maxLines: 1), + leading: CircleAvatar( + backgroundImage: NetworkImage(room.avatar.getThumbnail( + FamedlySdkExampleApp.client, + width: 64, + height: 64, + )), + ), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ChatView(room: room), + ), + ), + ); + }, + ), + ), + ); + } +} + +class ChatView extends StatefulWidget { + final Room room; + + const ChatView({Key key, @required this.room}) : super(key: key); + + @override + _ChatViewState createState() => _ChatViewState(); +} + +class _ChatViewState extends State { + final TextEditingController _controller = TextEditingController(); + + void _sendAction() { + print('Send Text'); + widget.room.sendTextEvent(_controller.text); + _controller.clear(); + } + + Timeline timeline; + + Future getTimeline() async { + timeline ??= + await widget.room.getTimeline(onUpdate: () => setState(() => null)); + return true; + } + + @override + void dispose() { + timeline?.cancelSubscriptions(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: StreamBuilder( + stream: widget.room.onUpdate.stream, + builder: (context, snapshot) { + return Text(widget.room.displayname); + }), + ), + body: Column( + children: [ + Expanded( + child: FutureBuilder( + future: getTimeline(), + builder: (context, snapshot) => !snapshot.hasData + ? Center( + child: CircularProgressIndicator(), + ) + : ListView.builder( + reverse: true, + itemCount: timeline.events.length, + itemBuilder: (BuildContext context, int i) => Opacity( + opacity: timeline.events[i].status != 2 ? 0.5 : 1, + child: ListTile( + title: Row( + children: [ + Expanded( + child: Text( + timeline.events[i].sender.calcDisplayname(), + ), + ), + Text( + timeline.events[i].originServerTs + .toIso8601String(), + style: TextStyle(fontSize: 12), + ), + ], + ), + subtitle: Text(timeline.events[i].body), + leading: CircleAvatar( + child: timeline.events[i].sender?.avatarUrl == null + ? Icon(Icons.person) + : null, + backgroundImage: + timeline.events[i].sender?.avatarUrl != null + ? NetworkImage( + timeline.events[i].sender?.avatarUrl + ?.getThumbnail( + FamedlySdkExampleApp.client, + width: 64, + height: 64, + ), + ) + : null, + ), + ), + ), + ), + ), + ), + Container( + height: 60, + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + labelText: 'Send a message ...', + ), + ), + ), + IconButton( + icon: Icon(Icons.send), + onPressed: _sendAction, + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index 5f3bc08..5a38b39 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -215,7 +215,7 @@ class Timeline { // Redaction events are handled as modification for existing events. if (eventUpdate.eventType == EventTypes.Redaction) { final eventId = _findEvent(event_id: eventUpdate.content['redacts']); - if (eventId != null) { + if (eventId < events.length) { removeAggregatedEvent(events[eventId]); events[eventId].setRedactionEvent(Event.fromJson( eventUpdate.content, room, eventUpdate.sortOrder)); @@ -262,9 +262,10 @@ class Timeline { } } sortAndUpdate(); - } catch (e) { + } catch (e, s) { if (room.client.debug) { print('[WARNING] (_handleEventUpdate) ${e.toString()}'); + print(s); } } }