import 'dart:async'; import 'package:animations/animations.dart'; import 'package:app_links/app_links.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/pages/add_app.dart'; import 'package:obtainium/pages/apps.dart'; import 'package:obtainium/pages/import_export.dart'; import 'package:obtainium/pages/settings.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; import 'package:provider/provider.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class NavigationPageItem { late String title; late IconData icon; late Widget widget; NavigationPageItem(this.title, this.icon, this.widget); } class _HomePageState extends State { List selectedIndexHistory = []; bool isReversing = false; int prevAppCount = -1; bool prevIsLoading = true; late AppLinks _appLinks; StreamSubscription? _linkSubscription; bool isLinkActivity = false; List pages = [ NavigationPageItem(tr('appsString'), Icons.apps, AppsPage(key: GlobalKey())), NavigationPageItem( tr('addApp'), Icons.add, AddAppPage(key: GlobalKey())), NavigationPageItem( tr('importExport'), Icons.import_export, const ImportExportPage()), NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage()) ]; @override void initState() { super.initState(); initDeepLinks(); } Future initDeepLinks() async { _appLinks = AppLinks(); goToAddApp(String data) async { switchToPage(1); while ( (pages[1].widget.key as GlobalKey?)?.currentState == null) { await Future.delayed(const Duration(microseconds: 1)); } (pages[1].widget.key as GlobalKey?) ?.currentState ?.linkFn(data); } interpretLink(Uri uri) async { isLinkActivity = true; var action = uri.host; var data = uri.path.length > 1 ? uri.path.substring(1) : ""; try { if (action == 'add') { await goToAddApp(data); } else if (action == 'app') { await context .read() .import('{ "apps": [${Uri.decodeComponent(data)}] }'); } else if (action == 'apps') { await context .read() .import('{ "apps": ${Uri.decodeComponent(data)} }'); } else { throw ObtainiumError(tr('unknown')); } } catch (e) { showError(e, context); } } // Check initial link if app was in cold state (terminated) final appLink = await _appLinks.getInitialAppLink(); if (appLink != null) { await interpretLink(appLink); } // Handle link when app is in warm state (front or background) _linkSubscription = _appLinks.uriLinkStream.listen((uri) async { await interpretLink(uri); }); } setIsReversing(int targetIndex) { bool reversing = selectedIndexHistory.isNotEmpty && selectedIndexHistory.last > targetIndex; setState(() { isReversing = reversing; }); } switchToPage(int index) async { setIsReversing(index); if (index == 0) { while ((pages[0].widget.key as GlobalKey).currentState != null) { // Avoid duplicate GlobalKey error await Future.delayed(const Duration(microseconds: 1)); } setState(() { selectedIndexHistory.clear(); }); } else if (selectedIndexHistory.isEmpty || (selectedIndexHistory.isNotEmpty && selectedIndexHistory.last != index)) { setState(() { int existingInd = selectedIndexHistory.indexOf(index); if (existingInd >= 0) { selectedIndexHistory.removeAt(existingInd); } selectedIndexHistory.add(index); }); } } @override Widget build(BuildContext context) { AppsProvider appsProvider = context.watch(); SettingsProvider settingsProvider = context.watch(); if (!prevIsLoading && prevAppCount >= 0 && appsProvider.apps.length > prevAppCount && selectedIndexHistory.isNotEmpty && selectedIndexHistory.last == 1 && !isLinkActivity) { switchToPage(0); } prevAppCount = appsProvider.apps.length; prevIsLoading = appsProvider.loadingApps; return WillPopScope( child: Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: PageTransitionSwitcher( duration: Duration( milliseconds: settingsProvider.disablePageTransitions ? 0 : 300), reverse: settingsProvider.reversePageTransitions ? !isReversing : isReversing, transitionBuilder: ( Widget child, Animation animation, Animation secondaryAnimation, ) { return SharedAxisTransition( animation: animation, secondaryAnimation: secondaryAnimation, transitionType: SharedAxisTransitionType.horizontal, child: child, ); }, child: pages .elementAt(selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last) .widget, ), bottomNavigationBar: NavigationBar( destinations: pages .map((e) => NavigationDestination(icon: Icon(e.icon), label: e.title)) .toList(), onDestinationSelected: (int index) async { HapticFeedback.selectionClick(); switchToPage(index); }, selectedIndex: selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last, ), ), onWillPop: () async { if (isLinkActivity && selectedIndexHistory.length == 1 && selectedIndexHistory.last == 1) { return true; } setIsReversing(selectedIndexHistory.length >= 2 ? selectedIndexHistory.reversed.toList()[1] : 0); if (selectedIndexHistory.isNotEmpty) { setState(() { selectedIndexHistory.removeLast(); }); return false; } return !(pages[0].widget.key as GlobalKey) .currentState ?.clearSelected(); }); } @override void dispose() { super.dispose(); _linkSubscription?.cancel(); } }