From 445f3f20f073c9c82a279544e73963da048c9563 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:31:38 +0530 Subject: [PATCH 1/3] Add option to install manually --- lib/l10n/arb/app_en.arb | 2 + lib/store/offline_authenticator_db.dart | 170 ++++++++++++++++++++++++ lib/ui/settings/app_update_dialog.dart | 19 +++ 3 files changed, 191 insertions(+) create mode 100644 lib/store/offline_authenticator_db.dart diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e5ac80b1ca..bd58abbe77 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -253,6 +253,8 @@ "privacy": "Privacy", "terms": "Terms", "checkForUpdates": "Check for updates", + "installManually": "Install manually", + "update": "Update", "checking": "Checking...", "youAreOnTheLatestVersion": "You are on the latest version", "warning": "Warning", diff --git a/lib/store/offline_authenticator_db.dart b/lib/store/offline_authenticator_db.dart new file mode 100644 index 0000000000..393e5302c7 --- /dev/null +++ b/lib/store/offline_authenticator_db.dart @@ -0,0 +1,170 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:ente_auth/models/authenticator/auth_entity.dart'; +import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite/sqflite.dart'; + +class OfflineAuthenticatorDB { + static const _databaseName = "ente.offline_authenticator.db"; + static const _databaseVersion = 1; + + static const entityTable = 'entities'; + + OfflineAuthenticatorDB._privateConstructor(); + static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor(); + + static Future? _dbFuture; + + Future get database async { + _dbFuture ??= _initDatabase(); + return _dbFuture!; + } + + Future _initDatabase() async { + final Directory documentsDirectory = + await getApplicationDocumentsDirectory(); + final String path = join(documentsDirectory.path, _databaseName); + debugPrint(path); + return await openDatabase( + path, + version: _databaseVersion, + onCreate: _onCreate, + ); + } + + Future _onCreate(Database db, int version) async { + await db.execute( + ''' + CREATE TABLE $entityTable ( + _generatedID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + id TEXT, + encryptedData TEXT NOT NULL, + header TEXT NOT NULL, + createdAt INTEGER NOT NULL, + updatedAt INTEGER NOT NULL, + shouldSync INTEGER DEFAULT 0, + UNIQUE(id) + ); + ''', + ); + } + + Future insert(String encData, String header) async { + final db = await instance.database; + final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; + final insertedID = await db.insert( + entityTable, + { + "encryptedData": encData, + "header": header, + "shouldSync": 1, + "createdAt": timeInMicroSeconds, + "updatedAt": timeInMicroSeconds, + }, + ); + return insertedID; + } + + Future updateEntry( + int generatedID, + String encData, + String header, + ) async { + final db = await instance.database; + final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; + int affectedRows = await db.update( + entityTable, + { + "encryptedData": encData, + "header": header, + "shouldSync": 1, + "updatedAt": timeInMicroSeconds, + }, + where: '_generatedID = ?', + whereArgs: [generatedID], + ); + return affectedRows; + } + + Future insertOrReplace(List authEntities) async { + final db = await instance.database; + final batch = db.batch(); + for (AuthEntity authEntity in authEntities) { + final insertRow = authEntity.toMap(); + insertRow.remove('isDeleted'); + insertRow.putIfAbsent('shouldSync', () => 0); + batch.insert( + entityTable, + insertRow, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + await batch.commit(noResult: true); + } + + Future updateLocalEntity(LocalAuthEntity localAuthEntity) async { + final db = await instance.database; + await db.update( + entityTable, + localAuthEntity.toMap(), + where: '_generatedID = ?', + whereArgs: [localAuthEntity.generatedID], + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + Future getEntryByID(int genID) async { + final db = await instance.database; + final rows = await db + .query(entityTable, where: '_generatedID = ?', whereArgs: [genID]); + final listOfAuthEntities = _convertRows(rows); + if (listOfAuthEntities.isEmpty) { + return null; + } else { + return listOfAuthEntities.first; + } + } + + Future> getAll() async { + final db = await instance.database; + final rows = await db.rawQuery("SELECT * from $entityTable"); + return _convertRows(rows); + } + +// deleteByID will prefer generated id if both ids are passed during deletion + Future deleteByIDs({List? generatedIDs, List? ids}) async { + final db = await instance.database; + final batch = db.batch(); + const whereGenID = '_generatedID = ?'; + const whereID = 'id = ?'; + if (generatedIDs != null) { + for (int genId in generatedIDs) { + batch.delete(entityTable, where: whereGenID, whereArgs: [genId]); + } + } + if (ids != null) { + for (String id in ids) { + batch.delete(entityTable, where: whereID, whereArgs: [id]); + } + } + final result = await batch.commit(); + debugPrint("Done"); + } + + Future clearTable() async { + final db = await instance.database; + await db.delete(entityTable); + } + + List _convertRows(List> rows) { + final keys = []; + for (final row in rows) { + keys.add(LocalAuthEntity.fromMap(row)); + } + return keys; + } +} diff --git a/lib/ui/settings/app_update_dialog.dart b/lib/ui/settings/app_update_dialog.dart index c6286b1647..39fa04bc86 100644 --- a/lib/ui/settings/app_update_dialog.dart +++ b/lib/ui/settings/app_update_dialog.dart @@ -3,10 +3,12 @@ import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/network.dart'; import 'package:ente_auth/ente_theme_data.dart'; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/update_service.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:open_filex/open_filex.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class AppUpdateDialog extends StatefulWidget { final LatestVersionInfo? latestVersionInfo; @@ -82,6 +84,23 @@ class _AppUpdateDialogState extends State { child: const Text( "Update", ), + + ), + ), + const Padding(padding: EdgeInsets.all(8)), + Center( + child: InkWell( + child: Text( + context.l10n.installManually, + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(decoration: TextDecoration.underline), + ), + onTap: () => launchUrlString( + widget.latestVersionInfo!.url!, + mode: LaunchMode.externalApplication, + ), ), ), ], From 9d95bd16cd8a28bf20111aabcec7afb227006415 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 4 Sep 2023 09:35:38 +0530 Subject: [PATCH 2/3] By default, enable sentry in debugMode --- lib/core/logging/super_logging.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/logging/super_logging.dart b/lib/core/logging/super_logging.dart index 095e888f2f..7b62d0a705 100644 --- a/lib/core/logging/super_logging.dart +++ b/lib/core/logging/super_logging.dart @@ -310,7 +310,7 @@ class SuperLogging { if (_preferences.containsKey(keyShouldReportErrors)) { return _preferences.getBool(keyShouldReportErrors)!; } else { - return false; + return kDebugMode; } } From 8cbba78bcbc90329bbf732f2b413d47758cb9e76 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 4 Sep 2023 09:42:27 +0530 Subject: [PATCH 3/3] APK download: Switch to direct download option --- android/build.gradle | 2 +- lib/l10n/arb/app_en.arb | 4 +- lib/store/offline_authenticator_db.dart | 170 ------------------------ lib/ui/settings/app_update_dialog.dart | 90 ++++++------- 4 files changed, 48 insertions(+), 218 deletions(-) delete mode 100644 lib/store/offline_authenticator_db.dart diff --git a/android/build.gradle b/android/build.gradle index 249165164c..47890036d0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -24,6 +24,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index bd58abbe77..7a29ce4677 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -253,7 +253,9 @@ "privacy": "Privacy", "terms": "Terms", "checkForUpdates": "Check for updates", - "installManually": "Install manually", + "downloadUpdate": "Download", + "criticalUpdateAvailable": "Critical update available", + "updateAvailable": "Update available", "update": "Update", "checking": "Checking...", "youAreOnTheLatestVersion": "You are on the latest version", diff --git a/lib/store/offline_authenticator_db.dart b/lib/store/offline_authenticator_db.dart deleted file mode 100644 index 393e5302c7..0000000000 --- a/lib/store/offline_authenticator_db.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:ente_auth/models/authenticator/auth_entity.dart'; -import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; -import 'package:flutter/foundation.dart'; -import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:sqflite/sqflite.dart'; - -class OfflineAuthenticatorDB { - static const _databaseName = "ente.offline_authenticator.db"; - static const _databaseVersion = 1; - - static const entityTable = 'entities'; - - OfflineAuthenticatorDB._privateConstructor(); - static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor(); - - static Future? _dbFuture; - - Future get database async { - _dbFuture ??= _initDatabase(); - return _dbFuture!; - } - - Future _initDatabase() async { - final Directory documentsDirectory = - await getApplicationDocumentsDirectory(); - final String path = join(documentsDirectory.path, _databaseName); - debugPrint(path); - return await openDatabase( - path, - version: _databaseVersion, - onCreate: _onCreate, - ); - } - - Future _onCreate(Database db, int version) async { - await db.execute( - ''' - CREATE TABLE $entityTable ( - _generatedID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - id TEXT, - encryptedData TEXT NOT NULL, - header TEXT NOT NULL, - createdAt INTEGER NOT NULL, - updatedAt INTEGER NOT NULL, - shouldSync INTEGER DEFAULT 0, - UNIQUE(id) - ); - ''', - ); - } - - Future insert(String encData, String header) async { - final db = await instance.database; - final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; - final insertedID = await db.insert( - entityTable, - { - "encryptedData": encData, - "header": header, - "shouldSync": 1, - "createdAt": timeInMicroSeconds, - "updatedAt": timeInMicroSeconds, - }, - ); - return insertedID; - } - - Future updateEntry( - int generatedID, - String encData, - String header, - ) async { - final db = await instance.database; - final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; - int affectedRows = await db.update( - entityTable, - { - "encryptedData": encData, - "header": header, - "shouldSync": 1, - "updatedAt": timeInMicroSeconds, - }, - where: '_generatedID = ?', - whereArgs: [generatedID], - ); - return affectedRows; - } - - Future insertOrReplace(List authEntities) async { - final db = await instance.database; - final batch = db.batch(); - for (AuthEntity authEntity in authEntities) { - final insertRow = authEntity.toMap(); - insertRow.remove('isDeleted'); - insertRow.putIfAbsent('shouldSync', () => 0); - batch.insert( - entityTable, - insertRow, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } - await batch.commit(noResult: true); - } - - Future updateLocalEntity(LocalAuthEntity localAuthEntity) async { - final db = await instance.database; - await db.update( - entityTable, - localAuthEntity.toMap(), - where: '_generatedID = ?', - whereArgs: [localAuthEntity.generatedID], - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } - - Future getEntryByID(int genID) async { - final db = await instance.database; - final rows = await db - .query(entityTable, where: '_generatedID = ?', whereArgs: [genID]); - final listOfAuthEntities = _convertRows(rows); - if (listOfAuthEntities.isEmpty) { - return null; - } else { - return listOfAuthEntities.first; - } - } - - Future> getAll() async { - final db = await instance.database; - final rows = await db.rawQuery("SELECT * from $entityTable"); - return _convertRows(rows); - } - -// deleteByID will prefer generated id if both ids are passed during deletion - Future deleteByIDs({List? generatedIDs, List? ids}) async { - final db = await instance.database; - final batch = db.batch(); - const whereGenID = '_generatedID = ?'; - const whereID = 'id = ?'; - if (generatedIDs != null) { - for (int genId in generatedIDs) { - batch.delete(entityTable, where: whereGenID, whereArgs: [genId]); - } - } - if (ids != null) { - for (String id in ids) { - batch.delete(entityTable, where: whereID, whereArgs: [id]); - } - } - final result = await batch.commit(); - debugPrint("Done"); - } - - Future clearTable() async { - final db = await instance.database; - await db.delete(entityTable); - } - - List _convertRows(List> rows) { - final keys = []; - for (final row in rows) { - keys.add(LocalAuthEntity.fromMap(row)); - } - return keys; - } -} diff --git a/lib/ui/settings/app_update_dialog.dart b/lib/ui/settings/app_update_dialog.dart index 39fa04bc86..eba357285b 100644 --- a/lib/ui/settings/app_update_dialog.dart +++ b/lib/ui/settings/app_update_dialog.dart @@ -1,10 +1,11 @@ - +import 'dart:io'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/network.dart'; import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/update_service.dart'; +import 'package:ente_auth/theme/ente_theme.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:open_filex/open_filex.dart'; @@ -22,6 +23,7 @@ class AppUpdateDialog extends StatefulWidget { class _AppUpdateDialogState extends State { @override Widget build(BuildContext context) { + final enteTextTheme = getEnteTextTheme(context); final List changelog = []; for (final log in widget.latestVersionInfo!.changelog) { changelog.add( @@ -43,18 +45,19 @@ class _AppUpdateDialogState extends State { Text( widget.latestVersionInfo!.name!, style: const TextStyle( - fontSize: 20, + fontSize: 14, fontWeight: FontWeight.bold, ), ), const Padding(padding: EdgeInsets.all(8)), - const Text( - "Changelog", - style: TextStyle( - fontSize: 18, + if (changelog.isNotEmpty) + const Text( + "Changelog", + style: TextStyle( + fontSize: 18, + ), ), - ), - const Padding(padding: EdgeInsets.all(4)), + if (changelog.isNotEmpty) const Padding(padding: EdgeInsets.all(4)), Column( crossAxisAlignment: CrossAxisAlignment.start, children: changelog, @@ -71,36 +74,13 @@ class _AppUpdateDialogState extends State { }, ), ), - onPressed: () async { - Navigator.pop(context); - showDialog( - context: context, - builder: (BuildContext context) { - return ApkDownloaderDialog(widget.latestVersionInfo); - }, - barrierDismissible: false, - ); - }, - child: const Text( - "Update", - ), - - ), - ), - const Padding(padding: EdgeInsets.all(8)), - Center( - child: InkWell( - child: Text( - context.l10n.installManually, - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(decoration: TextDecoration.underline), - ), - onTap: () => launchUrlString( + onPressed: () => launchUrlString( widget.latestVersionInfo!.url!, mode: LaunchMode.externalApplication, ), + child: Text( + context.l10n.downloadUpdate, + ), ), ), ], @@ -110,8 +90,24 @@ class _AppUpdateDialogState extends State { return WillPopScope( onWillPop: () async => !shouldForceUpdate, child: AlertDialog( - title: Text( - shouldForceUpdate ? "Critical update available" : "Update available", + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.auto_awesome_outlined, + size: 24, + color: getEnteColorScheme(context).strokeMuted, + ), + const SizedBox( + height: 16, + ), + Text( + shouldForceUpdate + ? context.l10n.criticalUpdateAvailable + : context.l10n.updateAvailable, + style: enteTextTheme.h3Bold, + ), + ], ), content: content, ), @@ -166,15 +162,17 @@ class _ApkDownloaderDialogState extends State { Future _downloadApk() async { try { - await Network.instance.getDio().download( - widget.versionInfo!.url!, - _saveUrl, - onReceiveProgress: (count, _) { - setState(() { - _downloadProgress = count / widget.versionInfo!.size!; - }); - }, - ); + if (!File(_saveUrl!).existsSync()) { + await Network.instance.getDio().download( + widget.versionInfo!.url!, + _saveUrl, + onReceiveProgress: (count, _) { + setState(() { + _downloadProgress = count / widget.versionInfo!.size!; + }); + }, + ); + } Navigator.of(context, rootNavigator: true).pop('dialog'); OpenFilex.open(_saveUrl); } catch (e) {