mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-31 04:50:16 +02:00
Allow for alternative app dirs (unfinished)
This commit is contained in:
@@ -338,9 +338,9 @@ class _AppPageState extends State<AppPage> {
|
|||||||
try {
|
try {
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
var res = await appsProvider.downloadAndInstallLatestApps(
|
var res = await appsProvider.downloadAndInstallLatestApps(
|
||||||
app?.app.id != null ? [app!.app.id] : [],
|
app?.app.id != null ? [app!.app.id] : [],
|
||||||
globalNavigatorKey.currentContext,
|
globalNavigatorKey.currentContext,
|
||||||
settingsProvider);
|
);
|
||||||
if (app?.app.installedVersion != null && !trackOnly) {
|
if (app?.app.installedVersion != null && !trackOnly) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
showError(tr('appsUpdated'), context);
|
showError(tr('appsUpdated'), context);
|
||||||
|
@@ -381,8 +381,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
: () {
|
: () {
|
||||||
appsProvider.downloadAndInstallLatestApps(
|
appsProvider.downloadAndInstallLatestApps(
|
||||||
[listedApps[appIndex].app.id],
|
[listedApps[appIndex].app.id],
|
||||||
globalNavigatorKey.currentContext,
|
globalNavigatorKey.currentContext).catchError((e) {
|
||||||
settingsProvider).catchError((e) {
|
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
return <String>[];
|
return <String>[];
|
||||||
});
|
});
|
||||||
@@ -699,8 +698,8 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
toInstall.addAll(trackOnlyUpdateIdsAllOrSelected);
|
toInstall.addAll(trackOnlyUpdateIdsAllOrSelected);
|
||||||
}
|
}
|
||||||
appsProvider
|
appsProvider
|
||||||
.downloadAndInstallLatestApps(toInstall,
|
.downloadAndInstallLatestApps(
|
||||||
globalNavigatorKey.currentContext, settingsProvider)
|
toInstall, globalNavigatorKey.currentContext)
|
||||||
.catchError((e) {
|
.catchError((e) {
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
return <String>[];
|
return <String>[];
|
||||||
|
@@ -150,6 +150,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
late Stream<FGBGType>? foregroundStream;
|
late Stream<FGBGType>? foregroundStream;
|
||||||
late StreamSubscription<FGBGType>? foregroundSubscription;
|
late StreamSubscription<FGBGType>? foregroundSubscription;
|
||||||
late Directory APKDir;
|
late Directory APKDir;
|
||||||
|
late SettingsProvider settingsProvider = SettingsProvider();
|
||||||
|
|
||||||
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
|
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
|
||||||
|
|
||||||
@@ -161,12 +162,12 @@ class AppsProvider with ChangeNotifier {
|
|||||||
if (isForeground) await loadApps();
|
if (isForeground) await loadApps();
|
||||||
});
|
});
|
||||||
() async {
|
() async {
|
||||||
|
await settingsProvider.initializeSettings();
|
||||||
var cacheDirs = await getExternalCacheDirectories();
|
var cacheDirs = await getExternalCacheDirectories();
|
||||||
if (cacheDirs?.isNotEmpty ?? false) {
|
if (cacheDirs?.isNotEmpty ?? false) {
|
||||||
APKDir = cacheDirs!.first;
|
APKDir = cacheDirs!.first;
|
||||||
} else {
|
} else {
|
||||||
APKDir =
|
APKDir = Directory('${await settingsProvider.getAppDir()}/apks');
|
||||||
Directory('${(await getExternalStorageDirectory())!.path}/apks');
|
|
||||||
if (!APKDir.existsSync()) {
|
if (!APKDir.existsSync()) {
|
||||||
APKDir.createSync();
|
APKDir.createSync();
|
||||||
}
|
}
|
||||||
@@ -369,8 +370,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
.where((element) => element.downloadProgress != null)
|
.where((element) => element.downloadProgress != null)
|
||||||
.isNotEmpty;
|
.isNotEmpty;
|
||||||
|
|
||||||
Future<bool> canInstallSilently(
|
Future<bool> canInstallSilently(App app) async {
|
||||||
App app, SettingsProvider settingsProvider) async {
|
|
||||||
if (app.id == obtainiumId) {
|
if (app.id == obtainiumId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -539,7 +539,6 @@ class AppsProvider with ChangeNotifier {
|
|||||||
getHost(apkUrl.value) != getHost(app.url) &&
|
getHost(apkUrl.value) != getHost(app.url) &&
|
||||||
context != null) {
|
context != null) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
var settingsProvider = context.read<SettingsProvider>();
|
|
||||||
if (!(settingsProvider.hideAPKOriginWarning) &&
|
if (!(settingsProvider.hideAPKOriginWarning) &&
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
await showDialog(
|
await showDialog(
|
||||||
@@ -560,8 +559,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// If no BuildContext is provided, apps that require user interaction are ignored
|
// If no BuildContext is provided, apps that require user interaction are ignored
|
||||||
// If user input is needed and the App is in the background, a notification is sent to get the user's attention
|
// If user input is needed and the App is in the background, a notification is sent to get the user's attention
|
||||||
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
||||||
Future<List<String>> downloadAndInstallLatestApps(List<String> appIds,
|
Future<List<String>> downloadAndInstallLatestApps(
|
||||||
BuildContext? context, SettingsProvider settingsProvider,
|
List<String> appIds, BuildContext? context,
|
||||||
{NotificationsProvider? notificationsProvider}) async {
|
{NotificationsProvider? notificationsProvider}) async {
|
||||||
notificationsProvider =
|
notificationsProvider =
|
||||||
notificationsProvider ?? context?.read<NotificationsProvider>();
|
notificationsProvider ?? context?.read<NotificationsProvider>();
|
||||||
@@ -590,8 +589,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
apps[id]!.app.preferredApkIndex = urlInd;
|
apps[id]!.app.preferredApkIndex = urlInd;
|
||||||
await saveApps([apps[id]!.app]);
|
await saveApps([apps[id]!.app]);
|
||||||
}
|
}
|
||||||
if (context != null ||
|
if (context != null || await canInstallSilently(apps[id]!.app)) {
|
||||||
await canInstallSilently(apps[id]!.app, settingsProvider)) {
|
|
||||||
appsToInstall.add(id);
|
appsToInstall.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,8 +626,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||||
}
|
}
|
||||||
var appId = downloadedFile?.appId ?? downloadedDir!.appId;
|
var appId = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||||
bool willBeSilent =
|
bool willBeSilent = await canInstallSilently(apps[appId]!.app);
|
||||||
await canInstallSilently(apps[appId]!.app, settingsProvider);
|
|
||||||
if (!(await settingsProvider.getInstallPermission(enforce: false))) {
|
if (!(await settingsProvider.getInstallPermission(enforce: false))) {
|
||||||
throw ObtainiumError(tr('cancelled'));
|
throw ObtainiumError(tr('cancelled'));
|
||||||
}
|
}
|
||||||
@@ -678,8 +675,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Directory> getAppsDir() async {
|
Future<Directory> getAppsDir() async {
|
||||||
Directory appsDir = Directory(
|
Directory appsDir =
|
||||||
'${(await getExternalStorageDirectory())?.path as String}/app_data');
|
Directory('${await settingsProvider.getAppDir()}/app_data');
|
||||||
if (!appsDir.existsSync()) {
|
if (!appsDir.existsSync()) {
|
||||||
appsDir.createSync();
|
appsDir.createSync();
|
||||||
}
|
}
|
||||||
@@ -879,8 +876,6 @@ class AppsProvider with ChangeNotifier {
|
|||||||
.toList();
|
.toList();
|
||||||
// After reconciliation, delete externally uninstalled Apps if needed
|
// After reconciliation, delete externally uninstalled Apps if needed
|
||||||
if (removedAppIds.isNotEmpty) {
|
if (removedAppIds.isNotEmpty) {
|
||||||
var settingsProvider = SettingsProvider();
|
|
||||||
await settingsProvider.initializeSettings();
|
|
||||||
if (settingsProvider.removeOnExternalUninstall) {
|
if (settingsProvider.removeOnExternalUninstall) {
|
||||||
await removeApps(removedAppIds);
|
await removeApps(removedAppIds);
|
||||||
}
|
}
|
||||||
@@ -1114,8 +1109,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
logs.add('Error accessing Downloads (will use fallback): $e');
|
logs.add('Error accessing Downloads (will use fallback): $e');
|
||||||
}
|
}
|
||||||
if (!downloadsAccessible) {
|
if (!downloadsAccessible) {
|
||||||
exportDir = await getExternalStorageDirectory();
|
exportDir = Directory(await settingsProvider.getAppDir());
|
||||||
path = exportDir!.path;
|
path = exportDir.path;
|
||||||
}
|
}
|
||||||
File export = File(
|
File export = File(
|
||||||
'${exportDir.path}/${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().millisecondsSinceEpoch}.json');
|
'${exportDir.path}/${tr('obtainiumExportHyphenatedLowercase')}-${DateTime.now().millisecondsSinceEpoch}.json');
|
||||||
@@ -1298,14 +1293,12 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
NotificationsProvider notificationsProvider = NotificationsProvider();
|
NotificationsProvider notificationsProvider = NotificationsProvider();
|
||||||
AppsProvider appsProvider = AppsProvider(isBg: true);
|
AppsProvider appsProvider = AppsProvider(isBg: true);
|
||||||
await appsProvider.loadApps();
|
await appsProvider.loadApps();
|
||||||
var settingsProvider = SettingsProvider();
|
|
||||||
await settingsProvider.initializeSettings();
|
|
||||||
|
|
||||||
int maxAttempts = 4;
|
int maxAttempts = 4;
|
||||||
|
|
||||||
params ??= {};
|
params ??= {};
|
||||||
if (params['toCheck'] == null) {
|
if (params['toCheck'] == null) {
|
||||||
settingsProvider.lastBGCheckTime = DateTime.now();
|
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
|
||||||
}
|
}
|
||||||
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
||||||
...(params['toCheck']
|
...(params['toCheck']
|
||||||
@@ -1335,7 +1328,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
var didCompleteChecking = false;
|
var didCompleteChecking = false;
|
||||||
CheckingUpdatesNotification? notif;
|
CheckingUpdatesNotification? notif;
|
||||||
var networkRestricted = false;
|
var networkRestricted = false;
|
||||||
if (settingsProvider.bgUpdatesOnWiFiOnly) {
|
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
||||||
var netResult = await (Connectivity().checkConnectivity());
|
var netResult = await (Connectivity().checkConnectivity());
|
||||||
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
||||||
(netResult != ConnectivityResult.ethernet);
|
(netResult != ConnectivityResult.ethernet);
|
||||||
@@ -1355,8 +1348,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
App? newApp = await appsProvider.checkUpdate(appId);
|
App? newApp = await appsProvider.checkUpdate(appId);
|
||||||
if (newApp != null) {
|
if (newApp != null) {
|
||||||
if (networkRestricted ||
|
if (networkRestricted ||
|
||||||
!(await appsProvider.canInstallSilently(
|
!(await appsProvider.canInstallSilently(app!.app))) {
|
||||||
app!.app, settingsProvider))) {
|
|
||||||
toNotify.add(newApp);
|
toNotify.add(newApp);
|
||||||
} else {
|
} else {
|
||||||
toInstall.add(MapEntry(appId, 0));
|
toInstall.add(MapEntry(appId, 0));
|
||||||
@@ -1442,8 +1434,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|||||||
try {
|
try {
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG install task $taskId: Attempting to update $appId in the background.');
|
'BG install task $taskId: Attempting to update $appId in the background.');
|
||||||
await appsProvider.downloadAndInstallLatestApps(
|
await appsProvider.downloadAndInstallLatestApps([appId], null,
|
||||||
[appId], null, settingsProvider,
|
|
||||||
notificationsProvider: notificationsProvider);
|
notificationsProvider: notificationsProvider);
|
||||||
await Future.delayed(const Duration(
|
await Future.delayed(const Duration(
|
||||||
seconds:
|
seconds:
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
// Exposes functions used to save/load app settings
|
// Exposes functions used to save/load app settings
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -9,8 +10,10 @@ import 'package:obtainium/app_sources/github.dart';
|
|||||||
import 'package:obtainium/main.dart';
|
import 'package:obtainium/main.dart';
|
||||||
import 'package:obtainium/providers/apps_provider.dart';
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:shared_storage/shared_storage.dart' as saf;
|
||||||
|
|
||||||
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
|
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
|
||||||
String obtainiumId = 'dev.imranr.obtainium';
|
String obtainiumId = 'dev.imranr.obtainium';
|
||||||
@@ -35,6 +38,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
|
|||||||
|
|
||||||
class SettingsProvider with ChangeNotifier {
|
class SettingsProvider with ChangeNotifier {
|
||||||
SharedPreferences? prefs;
|
SharedPreferences? prefs;
|
||||||
|
String? defaultAppDir;
|
||||||
bool justStarted = true;
|
bool justStarted = true;
|
||||||
|
|
||||||
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
||||||
@@ -42,6 +46,7 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
// Not done in constructor as we want to be able to await it
|
// Not done in constructor as we want to be able to await it
|
||||||
Future<void> initializeSettings() async {
|
Future<void> initializeSettings() async {
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
defaultAppDir = (await getExternalStorageDirectory())!.path;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,4 +362,53 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
prefs?.setBool('highlightTouchTargets', val);
|
prefs?.setBool('highlightTouchTargets', val);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getAppDir() async {
|
||||||
|
return prefs?.getString('appDir') ?? defaultAppDir!;
|
||||||
|
}
|
||||||
|
|
||||||
|
pickAppDir({bool useDefault = false}) async {
|
||||||
|
var existingSAFPerms = (await saf.persistedUriPermissions()) ?? [];
|
||||||
|
var currentAppDir = await getAppDir();
|
||||||
|
if (currentAppDir != defaultAppDir) {
|
||||||
|
currentAppDir = currentAppDir.replaceFirst(
|
||||||
|
'/storage/emulated/0/', '/tree/primary%3A');
|
||||||
|
}
|
||||||
|
String? newAppDir;
|
||||||
|
if (!useDefault) {
|
||||||
|
var target = (await saf.openDocumentTree());
|
||||||
|
if (target != null) {
|
||||||
|
newAppDir = target.path
|
||||||
|
.replaceFirst('/tree/primary%3A', '/storage/emulated/0/');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newAppDir = defaultAppDir;
|
||||||
|
}
|
||||||
|
newAppDir ??= defaultAppDir;
|
||||||
|
if (currentAppDir != newAppDir) {
|
||||||
|
moveDirectoryContents(Directory(currentAppDir), Directory(newAppDir!));
|
||||||
|
prefs?.setString('appDir', newAppDir);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
for (var e in existingSAFPerms) {
|
||||||
|
await saf.releasePersistableUriPermission(e.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveDirectoryContents(Directory sourceDir, Directory destinationDir) {
|
||||||
|
if (!destinationDir.existsSync()) {
|
||||||
|
destinationDir.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
List<FileSystemEntity> contents = sourceDir.listSync();
|
||||||
|
for (FileSystemEntity entity in contents) {
|
||||||
|
String newPath = '${destinationDir.path}/${entity.uri.pathSegments.last}';
|
||||||
|
if (entity is File) {
|
||||||
|
entity.renameSync(newPath);
|
||||||
|
} else if (entity is Directory) {
|
||||||
|
Directory newDestinationDir = Directory(newPath);
|
||||||
|
moveDirectoryContents(entity, newDestinationDir);
|
||||||
|
entity.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -686,6 +686,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.1"
|
||||||
|
shared_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_storage
|
||||||
|
sha256: "7c65a9d64f0f5521256be974cfd74010af12196657cec9f9fb7b03b2f11bcaf6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@@ -65,6 +65,7 @@ dependencies:
|
|||||||
flutter_archive: ^5.0.0
|
flutter_archive: ^5.0.0
|
||||||
hsluv: ^1.1.3
|
hsluv: ^1.1.3
|
||||||
connectivity_plus: ^4.0.2
|
connectivity_plus: ^4.0.2
|
||||||
|
shared_storage: ^0.8.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user