mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-03 23:03:29 +01:00 
			
		
		
		
	Allow for alternative app dirs (unfinished)
This commit is contained in:
		@@ -338,9 +338,9 @@ class _AppPageState extends State<AppPage> {
 | 
			
		||||
                try {
 | 
			
		||||
                  HapticFeedback.heavyImpact();
 | 
			
		||||
                  var res = await appsProvider.downloadAndInstallLatestApps(
 | 
			
		||||
                      app?.app.id != null ? [app!.app.id] : [],
 | 
			
		||||
                      globalNavigatorKey.currentContext,
 | 
			
		||||
                      settingsProvider);
 | 
			
		||||
                    app?.app.id != null ? [app!.app.id] : [],
 | 
			
		||||
                    globalNavigatorKey.currentContext,
 | 
			
		||||
                  );
 | 
			
		||||
                  if (app?.app.installedVersion != null && !trackOnly) {
 | 
			
		||||
                    // ignore: use_build_context_synchronously
 | 
			
		||||
                    showError(tr('appsUpdated'), context);
 | 
			
		||||
 
 | 
			
		||||
@@ -381,8 +381,7 @@ class AppsPageState extends State<AppsPage> {
 | 
			
		||||
              : () {
 | 
			
		||||
                  appsProvider.downloadAndInstallLatestApps(
 | 
			
		||||
                      [listedApps[appIndex].app.id],
 | 
			
		||||
                      globalNavigatorKey.currentContext,
 | 
			
		||||
                      settingsProvider).catchError((e) {
 | 
			
		||||
                      globalNavigatorKey.currentContext).catchError((e) {
 | 
			
		||||
                    showError(e, context);
 | 
			
		||||
                    return <String>[];
 | 
			
		||||
                  });
 | 
			
		||||
@@ -699,8 +698,8 @@ class AppsPageState extends State<AppsPage> {
 | 
			
		||||
                    toInstall.addAll(trackOnlyUpdateIdsAllOrSelected);
 | 
			
		||||
                  }
 | 
			
		||||
                  appsProvider
 | 
			
		||||
                      .downloadAndInstallLatestApps(toInstall,
 | 
			
		||||
                          globalNavigatorKey.currentContext, settingsProvider)
 | 
			
		||||
                      .downloadAndInstallLatestApps(
 | 
			
		||||
                          toInstall, globalNavigatorKey.currentContext)
 | 
			
		||||
                      .catchError((e) {
 | 
			
		||||
                    showError(e, context);
 | 
			
		||||
                    return <String>[];
 | 
			
		||||
 
 | 
			
		||||
@@ -150,6 +150,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
  late Stream<FGBGType>? foregroundStream;
 | 
			
		||||
  late StreamSubscription<FGBGType>? foregroundSubscription;
 | 
			
		||||
  late Directory APKDir;
 | 
			
		||||
  late SettingsProvider settingsProvider = SettingsProvider();
 | 
			
		||||
 | 
			
		||||
  Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
 | 
			
		||||
 | 
			
		||||
@@ -161,12 +162,12 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      if (isForeground) await loadApps();
 | 
			
		||||
    });
 | 
			
		||||
    () async {
 | 
			
		||||
      await settingsProvider.initializeSettings();
 | 
			
		||||
      var cacheDirs = await getExternalCacheDirectories();
 | 
			
		||||
      if (cacheDirs?.isNotEmpty ?? false) {
 | 
			
		||||
        APKDir = cacheDirs!.first;
 | 
			
		||||
      } else {
 | 
			
		||||
        APKDir =
 | 
			
		||||
            Directory('${(await getExternalStorageDirectory())!.path}/apks');
 | 
			
		||||
        APKDir = Directory('${await settingsProvider.getAppDir()}/apks');
 | 
			
		||||
        if (!APKDir.existsSync()) {
 | 
			
		||||
          APKDir.createSync();
 | 
			
		||||
        }
 | 
			
		||||
@@ -369,8 +370,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      .where((element) => element.downloadProgress != null)
 | 
			
		||||
      .isNotEmpty;
 | 
			
		||||
 | 
			
		||||
  Future<bool> canInstallSilently(
 | 
			
		||||
      App app, SettingsProvider settingsProvider) async {
 | 
			
		||||
  Future<bool> canInstallSilently(App app) async {
 | 
			
		||||
    if (app.id == obtainiumId) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -539,7 +539,6 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
        getHost(apkUrl.value) != getHost(app.url) &&
 | 
			
		||||
        context != null) {
 | 
			
		||||
      // ignore: use_build_context_synchronously
 | 
			
		||||
      var settingsProvider = context.read<SettingsProvider>();
 | 
			
		||||
      if (!(settingsProvider.hideAPKOriginWarning) &&
 | 
			
		||||
          // ignore: use_build_context_synchronously
 | 
			
		||||
          await showDialog(
 | 
			
		||||
@@ -560,8 +559,8 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
  // 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
 | 
			
		||||
  // Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
 | 
			
		||||
  Future<List<String>> downloadAndInstallLatestApps(List<String> appIds,
 | 
			
		||||
      BuildContext? context, SettingsProvider settingsProvider,
 | 
			
		||||
  Future<List<String>> downloadAndInstallLatestApps(
 | 
			
		||||
      List<String> appIds, BuildContext? context,
 | 
			
		||||
      {NotificationsProvider? notificationsProvider}) async {
 | 
			
		||||
    notificationsProvider =
 | 
			
		||||
        notificationsProvider ?? context?.read<NotificationsProvider>();
 | 
			
		||||
@@ -590,8 +589,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
          apps[id]!.app.preferredApkIndex = urlInd;
 | 
			
		||||
          await saveApps([apps[id]!.app]);
 | 
			
		||||
        }
 | 
			
		||||
        if (context != null ||
 | 
			
		||||
            await canInstallSilently(apps[id]!.app, settingsProvider)) {
 | 
			
		||||
        if (context != null || await canInstallSilently(apps[id]!.app)) {
 | 
			
		||||
          appsToInstall.add(id);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -628,8 +626,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
          downloadedDir = downloadedArtifact as DownloadedXApkDir;
 | 
			
		||||
        }
 | 
			
		||||
        var appId = downloadedFile?.appId ?? downloadedDir!.appId;
 | 
			
		||||
        bool willBeSilent =
 | 
			
		||||
            await canInstallSilently(apps[appId]!.app, settingsProvider);
 | 
			
		||||
        bool willBeSilent = await canInstallSilently(apps[appId]!.app);
 | 
			
		||||
        if (!(await settingsProvider.getInstallPermission(enforce: false))) {
 | 
			
		||||
          throw ObtainiumError(tr('cancelled'));
 | 
			
		||||
        }
 | 
			
		||||
@@ -678,8 +675,8 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Directory> getAppsDir() async {
 | 
			
		||||
    Directory appsDir = Directory(
 | 
			
		||||
        '${(await getExternalStorageDirectory())?.path as String}/app_data');
 | 
			
		||||
    Directory appsDir =
 | 
			
		||||
        Directory('${await settingsProvider.getAppDir()}/app_data');
 | 
			
		||||
    if (!appsDir.existsSync()) {
 | 
			
		||||
      appsDir.createSync();
 | 
			
		||||
    }
 | 
			
		||||
@@ -879,8 +876,6 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
          .toList();
 | 
			
		||||
      // After reconciliation, delete externally uninstalled Apps if needed
 | 
			
		||||
      if (removedAppIds.isNotEmpty) {
 | 
			
		||||
        var settingsProvider = SettingsProvider();
 | 
			
		||||
        await settingsProvider.initializeSettings();
 | 
			
		||||
        if (settingsProvider.removeOnExternalUninstall) {
 | 
			
		||||
          await removeApps(removedAppIds);
 | 
			
		||||
        }
 | 
			
		||||
@@ -1114,8 +1109,8 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      logs.add('Error accessing Downloads (will use fallback): $e');
 | 
			
		||||
    }
 | 
			
		||||
    if (!downloadsAccessible) {
 | 
			
		||||
      exportDir = await getExternalStorageDirectory();
 | 
			
		||||
      path = exportDir!.path;
 | 
			
		||||
      exportDir = Directory(await settingsProvider.getAppDir());
 | 
			
		||||
      path = exportDir.path;
 | 
			
		||||
    }
 | 
			
		||||
    File export = File(
 | 
			
		||||
        '${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();
 | 
			
		||||
  AppsProvider appsProvider = AppsProvider(isBg: true);
 | 
			
		||||
  await appsProvider.loadApps();
 | 
			
		||||
  var settingsProvider = SettingsProvider();
 | 
			
		||||
  await settingsProvider.initializeSettings();
 | 
			
		||||
 | 
			
		||||
  int maxAttempts = 4;
 | 
			
		||||
 | 
			
		||||
  params ??= {};
 | 
			
		||||
  if (params['toCheck'] == null) {
 | 
			
		||||
    settingsProvider.lastBGCheckTime = DateTime.now();
 | 
			
		||||
    appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
 | 
			
		||||
  }
 | 
			
		||||
  List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
 | 
			
		||||
    ...(params['toCheck']
 | 
			
		||||
@@ -1335,7 +1328,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
 | 
			
		||||
    var didCompleteChecking = false;
 | 
			
		||||
    CheckingUpdatesNotification? notif;
 | 
			
		||||
    var networkRestricted = false;
 | 
			
		||||
    if (settingsProvider.bgUpdatesOnWiFiOnly) {
 | 
			
		||||
    if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
 | 
			
		||||
      var netResult = await (Connectivity().checkConnectivity());
 | 
			
		||||
      networkRestricted = (netResult != ConnectivityResult.wifi) &&
 | 
			
		||||
          (netResult != ConnectivityResult.ethernet);
 | 
			
		||||
@@ -1355,8 +1348,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
 | 
			
		||||
            App? newApp = await appsProvider.checkUpdate(appId);
 | 
			
		||||
            if (newApp != null) {
 | 
			
		||||
              if (networkRestricted ||
 | 
			
		||||
                  !(await appsProvider.canInstallSilently(
 | 
			
		||||
                      app!.app, settingsProvider))) {
 | 
			
		||||
                  !(await appsProvider.canInstallSilently(app!.app))) {
 | 
			
		||||
                toNotify.add(newApp);
 | 
			
		||||
              } else {
 | 
			
		||||
                toInstall.add(MapEntry(appId, 0));
 | 
			
		||||
@@ -1442,8 +1434,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
 | 
			
		||||
      try {
 | 
			
		||||
        logs.add(
 | 
			
		||||
            'BG install task $taskId: Attempting to update $appId in the background.');
 | 
			
		||||
        await appsProvider.downloadAndInstallLatestApps(
 | 
			
		||||
            [appId], null, settingsProvider,
 | 
			
		||||
        await appsProvider.downloadAndInstallLatestApps([appId], null,
 | 
			
		||||
            notificationsProvider: notificationsProvider);
 | 
			
		||||
        await Future.delayed(const Duration(
 | 
			
		||||
            seconds:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
// Exposes functions used to save/load app settings
 | 
			
		||||
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:easy_localization/easy_localization.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/providers/apps_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:shared_preferences/shared_preferences.dart';
 | 
			
		||||
import 'package:shared_storage/shared_storage.dart' as saf;
 | 
			
		||||
 | 
			
		||||
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
 | 
			
		||||
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 {
 | 
			
		||||
  SharedPreferences? prefs;
 | 
			
		||||
  String? defaultAppDir;
 | 
			
		||||
  bool justStarted = true;
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
  Future<void> initializeSettings() async {
 | 
			
		||||
    prefs = await SharedPreferences.getInstance();
 | 
			
		||||
    defaultAppDir = (await getExternalStorageDirectory())!.path;
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -357,4 +362,53 @@ class SettingsProvider with ChangeNotifier {
 | 
			
		||||
    prefs?.setBool('highlightTouchTargets', val);
 | 
			
		||||
    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"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description: flutter
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,7 @@ dependencies:
 | 
			
		||||
  flutter_archive: ^5.0.0
 | 
			
		||||
  hsluv: ^1.1.3
 | 
			
		||||
  connectivity_plus: ^4.0.2
 | 
			
		||||
  shared_storage: ^0.8.0
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user