From f411a3e490ded18770f3f22e981fd78b151c2244 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:17:56 +0530 Subject: [PATCH 01/16] bump version to v0.3.49 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a55aec54d..39fa9ca45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: ente photos application # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.3.48+258 +version: 0.3.49+259 environment: sdk: ">=2.10.0 <3.0.0" From 7fb8b6690a3a54f6ea499a83db5317a30fc9601b Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:10:22 +0530 Subject: [PATCH 02/16] Add session model --- lib/models/sessions.dart | 127 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 lib/models/sessions.dart diff --git a/lib/models/sessions.dart b/lib/models/sessions.dart new file mode 100644 index 000000000..f10f892d4 --- /dev/null +++ b/lib/models/sessions.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +class Sessions { + final List sessions; + + Sessions( + this.sessions, + ); + + Sessions copyWith({ + List sessions, + }) { + return Sessions( + sessions ?? this.sessions, + ); + } + + Map toMap() { + return { + 'sessions': sessions?.map((x) => x.toMap())?.toList(), + }; + } + + factory Sessions.fromMap(Map map) { + return Sessions( + List.from(map['sessions']?.map((x) => Session.fromMap(x))), + ); + } + + String toJson() => json.encode(toMap()); + + factory Sessions.fromJson(String source) => + Sessions.fromMap(json.decode(source)); + + @override + String toString() => 'Sessions(sessions: $sessions)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Sessions && listEquals(other.sessions, sessions); + } + + @override + int get hashCode => sessions.hashCode; +} + +class Session { + final String token; + final int creationTime; + final String ip; + final String userAgent; + final int lastUsedTime; + + Session(this.token, this.creationTime, this.ip, this.userAgent, + this.lastUsedTime); + + Session copyWith({ + String token, + int creationTime, + String ip, + String userAgent, + int lastUsedTime, + }) { + return Session( + token ?? this.token, + creationTime ?? this.creationTime, + ip ?? this.ip, + userAgent ?? this.userAgent, + lastUsedTime ?? this.lastUsedTime, + ); + } + + Map toMap() { + return { + 'token': token, + 'creationTime': creationTime, + 'ip': ip, + 'userAgent': userAgent, + 'lastUsedTime': lastUsedTime, + }; + } + + factory Session.fromMap(Map map) { + return Session( + map['token'], + map['creationTime'], + map['ip'], + map['userAgent'], + map['lastUsedTime'], + ); + } + + String toJson() => json.encode(toMap()); + + factory Session.fromJson(String source) => + Session.fromMap(json.decode(source)); + + @override + String toString() { + return 'Session(token: $token, creationTime: $creationTime, ip: $ip, userAgent: $userAgent, lastUsedTime: $lastUsedTime)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Session && + other.token == token && + other.creationTime == creationTime && + other.ip == ip && + other.userAgent == userAgent && + other.lastUsedTime == lastUsedTime; + } + + @override + int get hashCode { + return token.hashCode ^ + creationTime.hashCode ^ + ip.hashCode ^ + userAgent.hashCode ^ + lastUsedTime.hashCode; + } +} From 96501f87789e1cbfed7af9aaa3a6a6c917eda70e Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:10:33 +0530 Subject: [PATCH 03/16] Add API to get active sessions --- lib/services/user_service.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart index ad20222c5..585856e11 100644 --- a/lib/services/user_service.dart +++ b/lib/services/user_service.dart @@ -15,6 +15,7 @@ import 'package:photos/events/user_details_changed_event.dart'; import 'package:photos/models/key_attributes.dart'; import 'package:photos/models/key_gen_result.dart'; import 'package:photos/models/public_key.dart'; +import 'package:photos/models/sessions.dart'; import 'package:photos/models/set_keys_request.dart'; import 'package:photos/models/set_recovery_key_request.dart'; import 'package:photos/models/user_details.dart'; @@ -121,6 +122,23 @@ class UserService { } } + Future getActiveSessions() async { + try { + final response = await _dio.get( + _config.getHttpEndpoint() + "/users/sessions", + options: Options( + headers: { + "X-Auth-Token": _config.getToken(), + }, + ), + ); + return Sessions.fromMap(response.data); + } on DioError catch (e) { + _logger.info(e); + rethrow; + } + } + Future logout(BuildContext context) async { final dialog = createProgressDialog(context, "logging out..."); await dialog.show(); From cef98e0fa7c608db12e9909c65890d0a77021dbf Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:10:50 +0530 Subject: [PATCH 04/16] Add interface to render active sessions --- lib/ui/sessions_page.dart | 96 ++++++++++ lib/ui/settings/security_section_widget.dart | 179 +++++++++++-------- pubspec.lock | 7 + pubspec.yaml | 1 + 4 files changed, 208 insertions(+), 75 deletions(-) create mode 100644 lib/ui/sessions_page.dart diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart new file mode 100644 index 000000000..1a6cff3b2 --- /dev/null +++ b/lib/ui/sessions_page.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:logging/logging.dart'; +import 'package:photos/models/sessions.dart'; +import 'package:photos/services/user_service.dart'; +import 'package:photos/ui/loading_widget.dart'; +import 'package:photos/utils/date_time_util.dart'; +import 'package:user_agent_parser/user_agent_parser.dart'; + +class SessionsPage extends StatefulWidget { + SessionsPage({Key key}) : super(key: key); + + @override + _SessionsPageState createState() => _SessionsPageState(); +} + +class _SessionsPageState extends State { + final _userAgentParser = UserAgentParser(); + Sessions _sessions; + + @override + void initState() { + _fetchActiveSessions(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("active sessions"), + ), + body: _getBody(), + ); + } + + Widget _getBody() { + if (_sessions == null) { + return Center(child: loadWidget); + } + List rows = []; + for (final session in _sessions.sessions) { + rows.add(_getSessionWidget(session)); + } + return Column(children: rows); + } + + Widget _getSessionWidget(Session session) { + final lastUsedTime = + DateTime.fromMicrosecondsSinceEpoch(session.lastUsedTime); + return Column( + children: [ + Padding(padding: EdgeInsets.all(8)), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_getPrettyUA(session)), + Padding(padding: EdgeInsets.all(4)), + Text( + session.ip, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + ), + ), + ], + ), + Text(getFormattedTime(lastUsedTime)), + ], + ), + ), + Padding(padding: EdgeInsets.all(8)), + Divider(), + ], + ); + } + + Future _fetchActiveSessions() async { + _sessions = await UserService.instance.getActiveSessions(); + for (final session in _sessions.sessions) { + Logger("Test").info(session.token); + } + setState(() {}); + } + + String _getPrettyUA(Session session) { + final parsedUA = _userAgentParser.parseResult(session.userAgent); + return parsedUA.browser == null + ? "Mobile" + : "Browser (" + parsedUA.browser.name + ")"; + } +} diff --git a/lib/ui/settings/security_section_widget.dart b/lib/ui/settings/security_section_widget.dart index 2cc446395..ca3dc07b6 100644 --- a/lib/ui/settings/security_section_widget.dart +++ b/lib/ui/settings/security_section_widget.dart @@ -9,7 +9,9 @@ import 'package:photos/events/two_factor_status_change_event.dart'; import 'package:photos/services/user_service.dart'; import 'package:photos/ui/app_lock.dart'; import 'package:photos/ui/loading_widget.dart'; +import 'package:photos/ui/sessions_page.dart'; import 'package:photos/ui/settings/settings_section_title.dart'; +import 'package:photos/ui/settings/settings_text_item.dart'; import 'package:photos/utils/auth_util.dart'; import 'package:photos/utils/toast_util.dart'; @@ -128,86 +130,113 @@ class _SecuritySectionWidgetState extends State { ), ]); if (Platform.isAndroid) { - children.addAll([ - Padding(padding: EdgeInsets.all(4)), - Divider(height: 4), - Padding(padding: EdgeInsets.all(4)), - SizedBox( - height: 36, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("hide from recents"), - Switch( - value: _config.shouldHideFromRecents(), - onChanged: (value) async { - if (value) { - AlertDialog alert = AlertDialog( - title: Text("hide from recents?"), - content: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Text( - "hiding from the task switcher will prevent you from taking screenshots in this app.", - style: TextStyle( - height: 1.5, + children.addAll( + [ + Padding(padding: EdgeInsets.all(4)), + Divider(height: 4), + Padding(padding: EdgeInsets.all(4)), + SizedBox( + height: 36, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("hide from recents"), + Switch( + value: _config.shouldHideFromRecents(), + onChanged: (value) async { + if (value) { + AlertDialog alert = AlertDialog( + title: Text("hide from recents?"), + content: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "hiding from the task switcher will prevent you from taking screenshots in this app.", + style: TextStyle( + height: 1.5, + ), ), - ), - Padding(padding: EdgeInsets.all(8)), - Text( - "are you sure?", - style: TextStyle( - height: 1.5, + Padding(padding: EdgeInsets.all(8)), + Text( + "are you sure?", + style: TextStyle( + height: 1.5, + ), ), - ), - ], + ], + ), ), - ), - actions: [ - TextButton( - child: - Text("no", style: TextStyle(color: Colors.white)), - onPressed: () { - Navigator.of(context, rootNavigator: true) - .pop('dialog'); - }, - ), - TextButton( - child: Text("yes", - style: TextStyle( - color: Colors.white.withOpacity(0.8))), - onPressed: () async { - Navigator.of(context, rootNavigator: true) - .pop('dialog'); - await _config.setShouldHideFromRecents(true); - await FlutterWindowManager.addFlags( - FlutterWindowManager.FLAG_SECURE); - setState(() {}); - }, - ), - ], - ); + actions: [ + TextButton( + child: Text("no", + style: TextStyle(color: Colors.white)), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop('dialog'); + }, + ), + TextButton( + child: Text("yes", + style: TextStyle( + color: Colors.white.withOpacity(0.8))), + onPressed: () async { + Navigator.of(context, rootNavigator: true) + .pop('dialog'); + await _config.setShouldHideFromRecents(true); + await FlutterWindowManager.addFlags( + FlutterWindowManager.FLAG_SECURE); + setState(() {}); + }, + ), + ], + ); - showDialog( - context: context, - builder: (BuildContext context) { - return alert; - }, - ); - } else { - await _config.setShouldHideFromRecents(false); - await FlutterWindowManager.clearFlags( - FlutterWindowManager.FLAG_SECURE); - setState(() {}); - } - }, - ), - ], + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } else { + await _config.setShouldHideFromRecents(false); + await FlutterWindowManager.clearFlags( + FlutterWindowManager.FLAG_SECURE); + setState(() {}); + } + }, + ), + ], + ), ), - ), - ]); + Padding(padding: EdgeInsets.all(4)), + Divider(height: 4), + Padding(padding: EdgeInsets.all(2)), + GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () async { + AppLock.of(context).setEnabled(false); + final result = await requestAuthentication(); + AppLock.of(context) + .setEnabled(Configuration.instance.shouldShowLockScreen()); + if (!result) { + showToast("please authenticate to view your active sessions"); + return; + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return SessionsPage(); + }, + ), + ); + }, + child: SettingsTextItem( + text: "active sessions", icon: Icons.navigate_next), + ), + ], + ); } return Column( children: children, diff --git a/pubspec.lock b/pubspec.lock index a2b31d93c..d8c0f0367 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1189,6 +1189,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + user_agent_parser: + dependency: "direct main" + description: + name: user_agent_parser + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" uuid: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 39fa9ca45..a2fa5edb1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,6 +97,7 @@ dependencies: syncfusion_flutter_sliders: ^19.2.49 uni_links: ^0.5.1 url_launcher: ^6.0.3 + user_agent_parser: ^0.0.1+2 uuid: ^3.0.4 video_player: path: thirdparty/plugins/packages/video_player/video_player From 3fbcd6a62d38ebce6374baf8737cc624cfa6449a Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:18:24 +0530 Subject: [PATCH 05/16] Add API to terminate a session --- lib/services/user_service.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart index 585856e11..f39e60397 100644 --- a/lib/services/user_service.dart +++ b/lib/services/user_service.dart @@ -139,6 +139,23 @@ class UserService { } } + Future terminateSession(String token) async { + try { + await _dio.delete(_config.getHttpEndpoint() + "/users/session", + options: Options( + headers: { + "X-Auth-Token": _config.getToken(), + }, + ), + queryParameters: { + "token": token, + }); + } on DioError catch (e) { + _logger.info(e); + rethrow; + } + } + Future logout(BuildContext context) async { final dialog = createProgressDialog(context, "logging out..."); await dialog.show(); From ae5cd428b8bdd55a99029534d99f5e00469e7678 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:24:47 +0530 Subject: [PATCH 06/16] Add hook to terminate a session --- lib/ui/sessions_page.dart | 107 +++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index 1a6cff3b2..409978ea3 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -5,6 +5,7 @@ import 'package:photos/models/sessions.dart'; import 'package:photos/services/user_service.dart'; import 'package:photos/ui/loading_widget.dart'; import 'package:photos/utils/date_time_util.dart'; +import 'package:photos/utils/dialog_util.dart'; import 'package:user_agent_parser/user_agent_parser.dart'; class SessionsPage extends StatefulWidget { @@ -48,37 +49,50 @@ class _SessionsPageState extends State { Widget _getSessionWidget(Session session) { final lastUsedTime = DateTime.fromMicrosecondsSinceEpoch(session.lastUsedTime); - return Column( - children: [ - Padding(padding: EdgeInsets.all(8)), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(_getPrettyUA(session)), - Padding(padding: EdgeInsets.all(4)), - Text( - session.ip, - style: TextStyle( - color: Colors.white.withOpacity(0.8), + return InkWell( + onTap: () async { + _showSessionTerminationDialog(session); + }, + child: Column( + children: [ + Padding(padding: EdgeInsets.all(8)), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_getPrettyUA(session)), + Padding(padding: EdgeInsets.all(4)), + Text( + session.ip, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + ), ), - ), - ], - ), - Text(getFormattedTime(lastUsedTime)), - ], + ], + ), + Text(getFormattedTime(lastUsedTime)), + ], + ), ), - ), - Padding(padding: EdgeInsets.all(8)), - Divider(), - ], + Padding(padding: EdgeInsets.all(8)), + Divider(), + ], + ), ); } + Future _terminateSession(Session session) async { + final dialog = createProgressDialog(context, "please wait..."); + await dialog.show(); + await UserService.instance.terminateSession(session.token); + await _fetchActiveSessions(); + await dialog.hide(); + } + Future _fetchActiveSessions() async { _sessions = await UserService.instance.getActiveSessions(); for (final session in _sessions.sessions) { @@ -87,6 +101,47 @@ class _SessionsPageState extends State { setState(() {}); } + void _showSessionTerminationDialog(Session session) { + AlertDialog alert = AlertDialog( + title: Text("terminate session?"), + content: Text( + "this will log you out of the selected device", + ), + actions: [ + TextButton( + child: Text( + "terminate", + style: TextStyle( + color: Colors.red, + ), + ), + onPressed: () async { + Navigator.of(context, rootNavigator: true).pop('dialog'); + _terminateSession(session); + }, + ), + TextButton( + child: Text( + "cancel", + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + Navigator.of(context, rootNavigator: true).pop('dialog'); + }, + ), + ], + ); + + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } + String _getPrettyUA(Session session) { final parsedUA = _userAgentParser.parseResult(session.userAgent); return parsedUA.browser == null From 1e5ecb919f7c7030d573e89954c0133767b94c7a Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:35:42 +0530 Subject: [PATCH 07/16] Add special hook to logout --- lib/ui/sessions_page.dart | 64 +++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index 409978ea3..45ecf884a 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; +import 'package:photos/core/configuration.dart'; import 'package:photos/models/sessions.dart'; import 'package:photos/services/user_service.dart'; import 'package:photos/ui/loading_widget.dart'; @@ -64,7 +65,7 @@ class _SessionsPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_getPrettyUA(session)), + _getUAWidget(session), Padding(padding: EdgeInsets.all(4)), Text( session.ip, @@ -95,18 +96,42 @@ class _SessionsPageState extends State { Future _fetchActiveSessions() async { _sessions = await UserService.instance.getActiveSessions(); - for (final session in _sessions.sessions) { - Logger("Test").info(session.token); - } + _sessions.sessions.sort((first, second) { + return second.lastUsedTime.compareTo(first.lastUsedTime); + }); setState(() {}); } void _showSessionTerminationDialog(Session session) { + final isLoggingOutFromThisDevice = + session.token == Configuration.instance.getToken(); + Widget text; + if (isLoggingOutFromThisDevice) { + text = Text( + "this will log you out of this device!", + ); + } else { + text = SingleChildScrollView( + child: Column( + children: [ + Text( + "this will log you out of the following device:", + ), + Padding(padding: EdgeInsets.all(8)), + Text( + session.userAgent, + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 14, + ), + ), + ], + ), + ); + } AlertDialog alert = AlertDialog( title: Text("terminate session?"), - content: Text( - "this will log you out of the selected device", - ), + content: text, actions: [ TextButton( child: Text( @@ -117,14 +142,20 @@ class _SessionsPageState extends State { ), onPressed: () async { Navigator.of(context, rootNavigator: true).pop('dialog'); - _terminateSession(session); + if (isLoggingOutFromThisDevice) { + await UserService.instance.logout(context); + } else { + _terminateSession(session); + } }, ), TextButton( child: Text( "cancel", style: TextStyle( - color: Colors.white, + color: isLoggingOutFromThisDevice + ? Theme.of(context).buttonColor + : Colors.white, ), ), onPressed: () { @@ -142,10 +173,19 @@ class _SessionsPageState extends State { ); } - String _getPrettyUA(Session session) { + Widget _getUAWidget(Session session) { + if (session.token == Configuration.instance.getToken()) { + return Text( + "this device", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).buttonColor, + ), + ); + } final parsedUA = _userAgentParser.parseResult(session.userAgent); - return parsedUA.browser == null + return Text(parsedUA.browser == null ? "Mobile" - : "Browser (" + parsedUA.browser.name + ")"; + : "Browser (" + parsedUA.browser.name + ")"); } } From 47c0ed7e2a9479df07cf092e90e239296d193b21 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:40:51 +0530 Subject: [PATCH 08/16] Fix InkWell bounds --- lib/ui/sessions_page.dart | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index 45ecf884a..8f5a6c19e 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -50,15 +50,14 @@ class _SessionsPageState extends State { Widget _getSessionWidget(Session session) { final lastUsedTime = DateTime.fromMicrosecondsSinceEpoch(session.lastUsedTime); - return InkWell( - onTap: () async { - _showSessionTerminationDialog(session); - }, - child: Column( - children: [ - Padding(padding: EdgeInsets.all(8)), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + return Column( + children: [ + InkWell( + onTap: () async { + _showSessionTerminationDialog(session); + }, + child: Padding( + padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -79,10 +78,9 @@ class _SessionsPageState extends State { ], ), ), - Padding(padding: EdgeInsets.all(8)), - Divider(), - ], - ), + ), + Divider(), + ], ); } From 08fa5a3d4315d355582228f3b1127f164b8c6440 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:50:47 +0530 Subject: [PATCH 09/16] Add parsing for desktop UA --- lib/ui/sessions_page.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index 8f5a6c19e..9014ebf32 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -182,8 +182,18 @@ class _SessionsPageState extends State { ); } final parsedUA = _userAgentParser.parseResult(session.userAgent); - return Text(parsedUA.browser == null - ? "Mobile" - : "Browser (" + parsedUA.browser.name + ")"); + if (session.userAgent.contains("ente")) { + return Text("Desktop"); + } else if (parsedUA.browser == null) { + if (session.userAgent.contains("Android")) { + return Text("Android"); + } + if (session.userAgent.contains("iPhone")) { + return Text("iPhone"); + } + return Text("Mobile"); + } else { + return Text("Browser (" + parsedUA.browser.name + ")"); + } } } From e55b440ac8427b2ce5818f4ce8140fcf1d34c759 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 01:51:20 +0530 Subject: [PATCH 10/16] Minor refactor --- lib/ui/sessions_page.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index 9014ebf32..24fe92d4d 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -181,10 +181,11 @@ class _SessionsPageState extends State { ), ); } - final parsedUA = _userAgentParser.parseResult(session.userAgent); if (session.userAgent.contains("ente")) { return Text("Desktop"); - } else if (parsedUA.browser == null) { + } + final parsedUA = _userAgentParser.parseResult(session.userAgent); + if (parsedUA.browser == null) { if (session.userAgent.contains("Android")) { return Text("Android"); } From e30f0383956ff3bc1618d7d63ea23eaeb58c5344 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 11:10:07 +0530 Subject: [PATCH 11/16] Accept review comments --- lib/models/sessions.dart | 33 +++++++++++++++++++-------- lib/ui/sessions_page.dart | 48 +++++++++++++++++---------------------- pubspec.lock | 7 ------ pubspec.yaml | 1 - 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/lib/models/sessions.dart b/lib/models/sessions.dart index f10f892d4..4371650c2 100644 --- a/lib/models/sessions.dart +++ b/lib/models/sessions.dart @@ -52,24 +52,33 @@ class Session { final String token; final int creationTime; final String ip; - final String userAgent; + final String ua; + final String prettyUA; final int lastUsedTime; - Session(this.token, this.creationTime, this.ip, this.userAgent, - this.lastUsedTime); + Session( + this.token, + this.creationTime, + this.ip, + this.ua, + this.prettyUA, + this.lastUsedTime, + ); Session copyWith({ String token, int creationTime, String ip, - String userAgent, + String ua, + String prettyUA, int lastUsedTime, }) { return Session( token ?? this.token, creationTime ?? this.creationTime, ip ?? this.ip, - userAgent ?? this.userAgent, + ua ?? this.ua, + prettyUA ?? this.prettyUA, lastUsedTime ?? this.lastUsedTime, ); } @@ -79,7 +88,8 @@ class Session { 'token': token, 'creationTime': creationTime, 'ip': ip, - 'userAgent': userAgent, + 'ua': ua, + 'prettyUA': prettyUA, 'lastUsedTime': lastUsedTime, }; } @@ -89,7 +99,8 @@ class Session { map['token'], map['creationTime'], map['ip'], - map['userAgent'], + map['ua'], + map['prettyUA'], map['lastUsedTime'], ); } @@ -101,7 +112,7 @@ class Session { @override String toString() { - return 'Session(token: $token, creationTime: $creationTime, ip: $ip, userAgent: $userAgent, lastUsedTime: $lastUsedTime)'; + return 'Session(token: $token, creationTime: $creationTime, ip: $ip, ua: $ua, prettyUA: $prettyUA, lastUsedTime: $lastUsedTime)'; } @override @@ -112,7 +123,8 @@ class Session { other.token == token && other.creationTime == creationTime && other.ip == ip && - other.userAgent == userAgent && + other.ua == ua && + other.prettyUA == prettyUA && other.lastUsedTime == lastUsedTime; } @@ -121,7 +133,8 @@ class Session { return token.hashCode ^ creationTime.hashCode ^ ip.hashCode ^ - userAgent.hashCode ^ + ua.hashCode ^ + prettyUA.hashCode ^ lastUsedTime.hashCode; } } diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index 24fe92d4d..df247547c 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:logging/logging.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/models/sessions.dart'; import 'package:photos/services/user_service.dart'; import 'package:photos/ui/loading_widget.dart'; import 'package:photos/utils/date_time_util.dart'; import 'package:photos/utils/dialog_util.dart'; -import 'package:user_agent_parser/user_agent_parser.dart'; class SessionsPage extends StatefulWidget { SessionsPage({Key key}) : super(key: key); @@ -17,7 +15,6 @@ class SessionsPage extends StatefulWidget { } class _SessionsPageState extends State { - final _userAgentParser = UserAgentParser(); Sessions _sessions; @override @@ -44,7 +41,11 @@ class _SessionsPageState extends State { for (final session in _sessions.sessions) { rows.add(_getSessionWidget(session)); } - return Column(children: rows); + return SingleChildScrollView( + child: Column( + children: rows, + ), + ); } Widget _getSessionWidget(Session session) { @@ -58,23 +59,30 @@ class _SessionsPageState extends State { }, child: Padding( padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + _getUAWidget(session), + Padding(padding: EdgeInsets.all(4)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _getUAWidget(session), - Padding(padding: EdgeInsets.all(4)), Text( session.ip, style: TextStyle( color: Colors.white.withOpacity(0.8), + fontSize: 14, + ), + ), + Text( + getFormattedTime(lastUsedTime), + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 12, ), ), ], ), - Text(getFormattedTime(lastUsedTime)), ], ), ), @@ -117,7 +125,7 @@ class _SessionsPageState extends State { ), Padding(padding: EdgeInsets.all(8)), Text( - session.userAgent, + session.ua, style: TextStyle( color: Colors.white.withOpacity(0.7), fontSize: 14, @@ -181,20 +189,6 @@ class _SessionsPageState extends State { ), ); } - if (session.userAgent.contains("ente")) { - return Text("Desktop"); - } - final parsedUA = _userAgentParser.parseResult(session.userAgent); - if (parsedUA.browser == null) { - if (session.userAgent.contains("Android")) { - return Text("Android"); - } - if (session.userAgent.contains("iPhone")) { - return Text("iPhone"); - } - return Text("Mobile"); - } else { - return Text("Browser (" + parsedUA.browser.name + ")"); - } + return Text(session.prettyUA); } } diff --git a/pubspec.lock b/pubspec.lock index d8c0f0367..a2b31d93c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1189,13 +1189,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - user_agent_parser: - dependency: "direct main" - description: - name: user_agent_parser - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1+2" uuid: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index a2fa5edb1..39fa9ca45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,7 +97,6 @@ dependencies: syncfusion_flutter_sliders: ^19.2.49 uni_links: ^0.5.1 url_launcher: ^6.0.3 - user_agent_parser: ^0.0.1+2 uuid: ^3.0.4 video_player: path: thirdparty/plugins/packages/video_player/video_player From c82ccc749ea3a6ce2a79ffeb115c015d5c9e3929 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Nov 2021 14:39:29 +0530 Subject: [PATCH 12/16] bump version to v0.3.50 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 39fa9ca45..cc3aa1f06 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: ente photos application # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.3.49+259 +version: 0.4.0+260 environment: sdk: ">=2.10.0 <3.0.0" From cc78c85b5bb3008ab067ed7045be41ac8fa5b2b0 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 17:01:08 +0530 Subject: [PATCH 13/16] Wrap over flowing text in sessions --- lib/ui/sessions_page.dart | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/ui/sessions_page.dart b/lib/ui/sessions_page.dart index df247547c..1d2370e46 100644 --- a/lib/ui/sessions_page.dart +++ b/lib/ui/sessions_page.dart @@ -67,18 +67,23 @@ class _SessionsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - session.ip, - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontSize: 14, + Flexible( + child: Text( + session.ip, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 14, + ), ), ), - Text( - getFormattedTime(lastUsedTime), - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontSize: 12, + Padding(padding: EdgeInsets.all(8)), + Flexible( + child: Text( + getFormattedTime(lastUsedTime), + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 12, + ), ), ), ], From 730a24cea5a30445620ad8ef48ad2a6d973113b5 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Wed, 24 Nov 2021 17:03:22 +0530 Subject: [PATCH 14/16] Wrap filename --- lib/ui/file_info_dialog.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/ui/file_info_dialog.dart b/lib/ui/file_info_dialog.dart index 759f0749b..656401827 100644 --- a/lib/ui/file_info_dialog.dart +++ b/lib/ui/file_info_dialog.dart @@ -182,9 +182,12 @@ class _FileInfoWidgetState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - file.getDisplayName(), + Flexible( + child: Text( + file.getDisplayName(), + ), ), + Padding(padding: EdgeInsets.all(8)), Icon( Icons.edit, color: Colors.white.withOpacity(0.85), From 7badc27f1b3ad8fdd9ceb2b6de989f95aa934dde Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:09:44 +0530 Subject: [PATCH 15/16] logout: wait for 5s before timing out existingSync --- lib/core/configuration.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index f9cc97dfb..9e492ba5e 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -123,7 +123,7 @@ class Configuration { if (SyncService.instance.isSyncInProgress()) { SyncService.instance.stopSync(); try { - await SyncService.instance.existingSync(); + await SyncService.instance.existingSync().timeout(Duration(seconds: 5)); } catch (e) { // ignore } From 1f80da066550cc2d2eeedbb357652f618a7d7a86 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:10:15 +0530 Subject: [PATCH 16/16] bump version to v0.4.1 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index cc3aa1f06..ce484e6f5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: ente photos application # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.4.0+260 +version: 0.4.1+261 environment: sdk: ">=2.10.0 <3.0.0"