mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-12 21:06:43 +02:00
683 lines
25 KiB
Dart
683 lines
25 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
import 'package:obtainium/components/generated_form_modal.dart';
|
|
import 'package:obtainium/custom_errors.dart';
|
|
import 'package:obtainium/main.dart';
|
|
import 'package:obtainium/pages/apps.dart';
|
|
import 'package:obtainium/pages/settings.dart';
|
|
import 'package:obtainium/providers/apps_provider.dart';
|
|
import 'package:obtainium/providers/settings_provider.dart';
|
|
import 'package:obtainium/providers/source_provider.dart';
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:markdown/markdown.dart' as md;
|
|
|
|
class AppPage extends StatefulWidget {
|
|
const AppPage({super.key, required this.appId});
|
|
|
|
final String appId;
|
|
|
|
@override
|
|
State<AppPage> createState() => _AppPageState();
|
|
}
|
|
|
|
class _AppPageState extends State<AppPage> {
|
|
late final WebViewController _webViewController;
|
|
bool _wasWebViewOpened = false;
|
|
AppInMemory? prevApp;
|
|
bool updating = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_webViewController = WebViewController()
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
..setNavigationDelegate(
|
|
NavigationDelegate(
|
|
onWebResourceError: (WebResourceError error) {
|
|
if (error.isForMainFrame == true) {
|
|
showError(
|
|
ObtainiumError(error.description, unexpected: true),
|
|
context,
|
|
);
|
|
}
|
|
},
|
|
onNavigationRequest: (NavigationRequest request) =>
|
|
!(request.url.startsWith("http://") ||
|
|
request.url.startsWith("https://") ||
|
|
request.url.startsWith("ftp://") ||
|
|
request.url.startsWith("ftps://"))
|
|
? NavigationDecision.prevent
|
|
: NavigationDecision.navigate,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
var appsProvider = context.watch<AppsProvider>();
|
|
var settingsProvider = context.watch<SettingsProvider>();
|
|
getUpdate(String id, {bool resetVersion = false}) async {
|
|
try {
|
|
setState(() {
|
|
updating = true;
|
|
});
|
|
await appsProvider.checkUpdate(id);
|
|
if (resetVersion) {
|
|
appsProvider.apps[id]?.app.additionalSettings['versionDetection'] =
|
|
true;
|
|
if (appsProvider.apps[id]?.app.installedVersion != null) {
|
|
appsProvider.apps[id]?.app.installedVersion =
|
|
appsProvider.apps[id]?.app.latestVersion;
|
|
}
|
|
appsProvider.saveApps([appsProvider.apps[id]!.app]);
|
|
}
|
|
} catch (err) {
|
|
// ignore: use_build_context_synchronously
|
|
showError(err, context);
|
|
} finally {
|
|
setState(() {
|
|
updating = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
bool areDownloadsRunning = appsProvider.areDownloadsRunning();
|
|
|
|
var sourceProvider = SourceProvider();
|
|
AppInMemory? app = appsProvider.apps[widget.appId]?.deepCopy();
|
|
var source = app != null
|
|
? sourceProvider.getSource(
|
|
app.app.url,
|
|
overrideSource: app.app.overrideSource,
|
|
)
|
|
: null;
|
|
if (!areDownloadsRunning &&
|
|
prevApp == null &&
|
|
app != null &&
|
|
settingsProvider.checkUpdateOnDetailPage) {
|
|
prevApp = app;
|
|
getUpdate(app.app.id);
|
|
}
|
|
var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
|
|
|
|
bool isVersionDetectionStandard =
|
|
app?.app.additionalSettings['versionDetection'] == true;
|
|
|
|
bool installedVersionIsEstimate = app?.app != null
|
|
? isVersionPseudo(app!.app)
|
|
: false;
|
|
|
|
if (app != null && !_wasWebViewOpened) {
|
|
_wasWebViewOpened = true;
|
|
_webViewController.loadRequest(Uri.parse(app.app.url));
|
|
}
|
|
|
|
getInfoColumn() {
|
|
String versionLines = '';
|
|
bool installed = app?.app.installedVersion != null;
|
|
bool upToDate = app?.app.installedVersion == app?.app.latestVersion;
|
|
if (installed) {
|
|
versionLines = '${app?.app.installedVersion} ${tr('installed')}';
|
|
if (upToDate) {
|
|
versionLines += '/${tr('latest')}';
|
|
}
|
|
} else {
|
|
versionLines = tr('notInstalled');
|
|
}
|
|
if (!upToDate) {
|
|
versionLines += '\n${app?.app.latestVersion} ${tr('latest')}';
|
|
}
|
|
String infoLines = tr(
|
|
'lastUpdateCheckX',
|
|
args: [
|
|
app?.app.lastUpdateCheck == null
|
|
? tr('never')
|
|
: '${app?.app.lastUpdateCheck?.toLocal()}',
|
|
],
|
|
);
|
|
if (trackOnly) {
|
|
infoLines = '${tr('xIsTrackOnly', args: [tr('app')])}\n$infoLines';
|
|
}
|
|
if (installedVersionIsEstimate) {
|
|
infoLines = '${tr('pseudoVersionInUse')}\n$infoLines';
|
|
}
|
|
if ((app?.app.apkUrls.length ?? 0) > 0) {
|
|
infoLines =
|
|
'$infoLines\n${app?.app.apkUrls.length == 1 ? app?.app.apkUrls[0].key : plural('apk', app?.app.apkUrls.length ?? 0)}';
|
|
}
|
|
var changeLogFn = app != null ? getChangeLogFn(context, app.app) : null;
|
|
return Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 24),
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
versionLines,
|
|
textAlign: TextAlign.start,
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.bold),
|
|
),
|
|
changeLogFn != null || app?.app.releaseDate != null
|
|
? GestureDetector(
|
|
onTap: changeLogFn,
|
|
child: Text(
|
|
app?.app.releaseDate == null
|
|
? tr('changes')
|
|
: app!.app.releaseDate!.toLocal().toString(),
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.labelSmall!
|
|
.copyWith(
|
|
decoration: changeLogFn != null
|
|
? TextDecoration.underline
|
|
: null,
|
|
fontStyle: changeLogFn != null
|
|
? FontStyle.italic
|
|
: null,
|
|
),
|
|
),
|
|
)
|
|
: const SizedBox.shrink(),
|
|
const SizedBox(height: 8),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
infoLines,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
|
|
),
|
|
if (app?.app.apkUrls.isNotEmpty == true ||
|
|
app?.app.otherAssetUrls.isNotEmpty == true)
|
|
GestureDetector(
|
|
onTap: app?.app == null || updating
|
|
? null
|
|
: () async {
|
|
try {
|
|
await appsProvider.downloadAppAssets([
|
|
app!.app.id,
|
|
], context);
|
|
} catch (e) {
|
|
showError(e, context);
|
|
}
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
color: settingsProvider.highlightTouchTargets
|
|
? (Theme.of(context).brightness == Brightness.light
|
|
? Theme.of(context).primaryColor
|
|
: Theme.of(context).primaryColorLight)
|
|
.withAlpha(
|
|
Theme.of(context).brightness ==
|
|
Brightness.light
|
|
? 20
|
|
: 40,
|
|
)
|
|
: null,
|
|
),
|
|
padding: settingsProvider.highlightTouchTargets
|
|
? const EdgeInsetsDirectional.fromSTEB(12, 6, 12, 6)
|
|
: const EdgeInsetsDirectional.fromSTEB(0, 6, 0, 6),
|
|
margin: const EdgeInsetsDirectional.fromSTEB(0, 6, 0, 0),
|
|
child: Text(
|
|
tr('downloadX', args: [tr('releaseAsset').toLowerCase()]),
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
|
decoration: TextDecoration.underline,
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 48),
|
|
CategoryEditorSelector(
|
|
alignment: WrapAlignment.center,
|
|
preselected: app?.app.categories != null
|
|
? app!.app.categories.toSet()
|
|
: {},
|
|
onSelected: (categories) {
|
|
if (app != null) {
|
|
app.app.categories = categories;
|
|
appsProvider.saveApps([app.app]);
|
|
}
|
|
},
|
|
),
|
|
if (app?.app.additionalSettings['about'] is String &&
|
|
app?.app.additionalSettings['about'].isNotEmpty)
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const SizedBox(height: 48),
|
|
GestureDetector(
|
|
onLongPress: () {
|
|
Clipboard.setData(
|
|
ClipboardData(
|
|
text: app?.app.additionalSettings['about'] ?? '',
|
|
),
|
|
);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(tr('copiedToClipboard'))),
|
|
);
|
|
},
|
|
child: Markdown(
|
|
physics: NeverScrollableScrollPhysics(),
|
|
shrinkWrap: true,
|
|
styleSheet: MarkdownStyleSheet(
|
|
blockquoteDecoration: BoxDecoration(
|
|
color: Theme.of(context).cardColor,
|
|
),
|
|
textAlign: WrapAlignment.center,
|
|
),
|
|
data: app?.app.additionalSettings['about'],
|
|
onTapLink: (text, href, title) {
|
|
if (href != null) {
|
|
launchUrlString(
|
|
href,
|
|
mode: LaunchMode.externalApplication,
|
|
);
|
|
}
|
|
},
|
|
extensionSet: md.ExtensionSet(
|
|
md.ExtensionSet.gitHubFlavored.blockSyntaxes,
|
|
[
|
|
md.EmojiSyntax(),
|
|
...md.ExtensionSet.gitHubFlavored.inlineSyntaxes,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
getFullInfoColumn({bool small = false}) => Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
SizedBox(height: small ? 5 : 20),
|
|
FutureBuilder(
|
|
future: appsProvider.updateAppIcon(app?.app.id, ignoreCache: true),
|
|
builder: (ctx, val) {
|
|
return app?.icon != null
|
|
? Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: app == null
|
|
? null
|
|
: () => pm.openApp(app.app.id),
|
|
child: Image.memory(
|
|
app!.icon!,
|
|
height: small ? 70 : 150,
|
|
gaplessPlayback: true,
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: Container();
|
|
},
|
|
),
|
|
SizedBox(height: small ? 10 : 25),
|
|
Text(
|
|
app?.name ?? tr('app'),
|
|
textAlign: TextAlign.center,
|
|
style: small
|
|
? Theme.of(context).textTheme.displaySmall
|
|
: Theme.of(context).textTheme.displayLarge,
|
|
),
|
|
Text(
|
|
tr('byX', args: [app?.author ?? tr('unknown')]),
|
|
textAlign: TextAlign.center,
|
|
style: small
|
|
? Theme.of(context).textTheme.headlineSmall
|
|
: Theme.of(context).textTheme.headlineMedium,
|
|
),
|
|
const SizedBox(height: 24),
|
|
GestureDetector(
|
|
onTap: () {
|
|
if (app?.app.url != null) {
|
|
launchUrlString(
|
|
app?.app.url ?? '',
|
|
mode: LaunchMode.externalApplication,
|
|
);
|
|
}
|
|
},
|
|
onLongPress: () {
|
|
Clipboard.setData(ClipboardData(text: app?.app.url ?? ''));
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text(tr('copiedToClipboard'))));
|
|
},
|
|
child: Text(
|
|
app?.app.url ?? '',
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
|
decoration: TextDecoration.underline,
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
),
|
|
Text(
|
|
app?.app.id ?? '',
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.labelSmall,
|
|
),
|
|
getInfoColumn(),
|
|
const SizedBox(height: 150),
|
|
],
|
|
);
|
|
|
|
getAppWebView() => app != null
|
|
? WebViewWidget(
|
|
key: ObjectKey(_webViewController),
|
|
controller: _webViewController
|
|
..setBackgroundColor(Theme.of(context).colorScheme.surface),
|
|
)
|
|
: Container();
|
|
|
|
showMarkUpdatedDialog() {
|
|
return showDialog(
|
|
context: context,
|
|
builder: (BuildContext ctx) {
|
|
return AlertDialog(
|
|
title: Text(tr('alreadyUpToDateQuestion')),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text(tr('no')),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
HapticFeedback.selectionClick();
|
|
var updatedApp = app?.app;
|
|
if (updatedApp != null) {
|
|
updatedApp.installedVersion = updatedApp.latestVersion;
|
|
appsProvider.saveApps([updatedApp]);
|
|
}
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text(tr('yesMarkUpdated')),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
showAdditionalOptionsDialog() async {
|
|
return await showDialog<Map<String, dynamic>?>(
|
|
context: context,
|
|
builder: (BuildContext ctx) {
|
|
var items = (source?.combinedAppSpecificSettingFormItems ?? []).map((
|
|
row,
|
|
) {
|
|
row = row.map((e) {
|
|
if (app?.app.additionalSettings[e.key] != null) {
|
|
e.defaultValue = app?.app.additionalSettings[e.key];
|
|
}
|
|
return e;
|
|
}).toList();
|
|
return row;
|
|
}).toList();
|
|
|
|
return GeneratedFormModal(
|
|
title: tr('additionalOptions'),
|
|
items: items,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
handleAdditionalOptionChanges(Map<String, dynamic>? values) {
|
|
if (app != null && values != null) {
|
|
Map<String, dynamic> originalSettings = app.app.additionalSettings;
|
|
app.app.additionalSettings = values;
|
|
if (source?.enforceTrackOnly == true) {
|
|
app.app.additionalSettings['trackOnly'] = true;
|
|
// ignore: use_build_context_synchronously
|
|
showMessage(tr('appsFromSourceAreTrackOnly'), context);
|
|
}
|
|
var versionDetectionEnabled =
|
|
app.app.additionalSettings['versionDetection'] == true &&
|
|
originalSettings['versionDetection'] != true;
|
|
var releaseDateVersionEnabled =
|
|
app.app.additionalSettings['releaseDateAsVersion'] == true &&
|
|
originalSettings['releaseDateAsVersion'] != true;
|
|
var releaseDateVersionDisabled =
|
|
app.app.additionalSettings['releaseDateAsVersion'] != true &&
|
|
originalSettings['releaseDateAsVersion'] == true;
|
|
if (releaseDateVersionEnabled) {
|
|
if (app.app.releaseDate != null) {
|
|
bool isUpdated = app.app.installedVersion == app.app.latestVersion;
|
|
app.app.latestVersion = app.app.releaseDate!.microsecondsSinceEpoch
|
|
.toString();
|
|
if (isUpdated) {
|
|
app.app.installedVersion = app.app.latestVersion;
|
|
}
|
|
}
|
|
} else if (releaseDateVersionDisabled) {
|
|
app.app.installedVersion =
|
|
app.installedInfo?.versionName ?? app.app.installedVersion;
|
|
}
|
|
if (versionDetectionEnabled) {
|
|
app.app.additionalSettings['versionDetection'] = true;
|
|
app.app.additionalSettings['releaseDateAsVersion'] = false;
|
|
}
|
|
appsProvider.saveApps([app.app]).then((value) {
|
|
getUpdate(app.app.id, resetVersion: versionDetectionEnabled);
|
|
});
|
|
}
|
|
}
|
|
|
|
getInstallOrUpdateButton() => TextButton(
|
|
onPressed:
|
|
!updating &&
|
|
(app?.app.installedVersion == null ||
|
|
app?.app.installedVersion != app?.app.latestVersion) &&
|
|
!areDownloadsRunning
|
|
? () async {
|
|
try {
|
|
var successMessage = app?.app.installedVersion == null
|
|
? tr('installed')
|
|
: tr('appsUpdated');
|
|
HapticFeedback.heavyImpact();
|
|
var res = await appsProvider.downloadAndInstallLatestApps(
|
|
app?.app.id != null ? [app!.app.id] : [],
|
|
globalNavigatorKey.currentContext,
|
|
);
|
|
if (res.isNotEmpty && !trackOnly) {
|
|
// ignore: use_build_context_synchronously
|
|
showMessage(successMessage, context);
|
|
}
|
|
if (res.isNotEmpty && mounted) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
} catch (e) {
|
|
// ignore: use_build_context_synchronously
|
|
showError(e, context);
|
|
}
|
|
}
|
|
: null,
|
|
child: Text(
|
|
app?.app.installedVersion == null
|
|
? !trackOnly
|
|
? tr('install')
|
|
: tr('markInstalled')
|
|
: !trackOnly
|
|
? tr('update')
|
|
: tr('markUpdated'),
|
|
),
|
|
);
|
|
|
|
getBottomSheetMenu() => Padding(
|
|
padding: EdgeInsets.fromLTRB(
|
|
0,
|
|
0,
|
|
0,
|
|
MediaQuery.of(context).padding.bottom,
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
if (source != null &&
|
|
source.combinedAppSpecificSettingFormItems.isNotEmpty)
|
|
IconButton(
|
|
onPressed: app?.downloadProgress != null || updating
|
|
? null
|
|
: () async {
|
|
var values = await showAdditionalOptionsDialog();
|
|
handleAdditionalOptionChanges(values);
|
|
},
|
|
tooltip: tr('additionalOptions'),
|
|
icon: const Icon(Icons.edit),
|
|
),
|
|
if (app != null && app.installedInfo != null)
|
|
IconButton(
|
|
onPressed: () {
|
|
appsProvider.openAppSettings(app.app.id);
|
|
},
|
|
icon: const Icon(Icons.settings),
|
|
tooltip: tr('settings'),
|
|
),
|
|
if (app != null && settingsProvider.showAppWebpage)
|
|
IconButton(
|
|
onPressed: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext ctx) {
|
|
return AlertDialog(
|
|
scrollable: true,
|
|
content: getFullInfoColumn(small: true),
|
|
title: Text(app.name),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text(tr('continue')),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
},
|
|
icon: const Icon(Icons.more_horiz),
|
|
tooltip: tr('more'),
|
|
),
|
|
if (app?.app.installedVersion != null &&
|
|
app?.app.installedVersion != app?.app.latestVersion &&
|
|
!isVersionDetectionStandard &&
|
|
!trackOnly)
|
|
IconButton(
|
|
onPressed: app?.downloadProgress != null || updating
|
|
? null
|
|
: showMarkUpdatedDialog,
|
|
tooltip: tr('markUpdated'),
|
|
icon: const Icon(Icons.done),
|
|
),
|
|
if ((!isVersionDetectionStandard || trackOnly) &&
|
|
app?.app.installedVersion != null &&
|
|
app?.app.installedVersion == app?.app.latestVersion)
|
|
IconButton(
|
|
onPressed: app?.app == null || updating
|
|
? null
|
|
: () {
|
|
app!.app.installedVersion = null;
|
|
appsProvider.saveApps([app.app]);
|
|
},
|
|
icon: const Icon(Icons.restore_rounded),
|
|
tooltip: tr('resetInstallStatus'),
|
|
),
|
|
const SizedBox(width: 16.0),
|
|
Expanded(child: getInstallOrUpdateButton()),
|
|
const SizedBox(width: 16.0),
|
|
IconButton(
|
|
onPressed: app?.downloadProgress != null || updating
|
|
? null
|
|
: () {
|
|
appsProvider
|
|
.removeAppsWithModal(
|
|
context,
|
|
app != null ? [app.app] : [],
|
|
)
|
|
.then((value) {
|
|
if (value == true) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
});
|
|
},
|
|
tooltip: tr('remove'),
|
|
icon: const Icon(Icons.delete_outline),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (app?.downloadProgress != null)
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
|
|
child: LinearProgressIndicator(
|
|
value: app!.downloadProgress! >= 0
|
|
? app.downloadProgress! / 100
|
|
: null,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
appScreenAppBar() => AppBar(
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
),
|
|
);
|
|
|
|
return Scaffold(
|
|
appBar: settingsProvider.showAppWebpage ? AppBar() : appScreenAppBar(),
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
body: RefreshIndicator(
|
|
child: settingsProvider.showAppWebpage
|
|
? getAppWebView()
|
|
: CustomScrollView(
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Column(children: [getFullInfoColumn()]),
|
|
),
|
|
],
|
|
),
|
|
onRefresh: () async {
|
|
if (app != null) {
|
|
getUpdate(app.app.id);
|
|
}
|
|
},
|
|
),
|
|
bottomSheet: getBottomSheetMenu(),
|
|
);
|
|
}
|
|
}
|