mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
fix: add changelog (wip) on each app item
This commit is contained in:
parent
45f4a5b207
commit
080ceae784
@ -397,7 +397,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
mapOf(
|
mapOf(
|
||||||
"progress" to 1.0,
|
"progress" to 1.0,
|
||||||
"header" to "Finished!",
|
"header" to "Finished!",
|
||||||
"log" to "Finished"
|
"log" to "Finished!"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,9 @@ class PatchedApplication {
|
|||||||
DateTime patchDate;
|
DateTime patchDate;
|
||||||
final bool isRooted;
|
final bool isRooted;
|
||||||
final bool isFromStorage;
|
final bool isFromStorage;
|
||||||
|
bool hasUpdates;
|
||||||
List<String> appliedPatches;
|
List<String> appliedPatches;
|
||||||
|
List<String> changelog;
|
||||||
|
|
||||||
PatchedApplication({
|
PatchedApplication({
|
||||||
required this.name,
|
required this.name,
|
||||||
@ -27,9 +29,11 @@ class PatchedApplication {
|
|||||||
required this.apkFilePath,
|
required this.apkFilePath,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.patchDate,
|
required this.patchDate,
|
||||||
required this.isRooted,
|
this.isRooted = false,
|
||||||
required this.isFromStorage,
|
this.isFromStorage = false,
|
||||||
required this.appliedPatches,
|
this.hasUpdates = false,
|
||||||
|
this.appliedPatches = const [],
|
||||||
|
this.changelog = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:github/github.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:revanced_manager/constants.dart';
|
import 'package:revanced_manager/constants.dart';
|
||||||
@ -14,9 +15,11 @@ class ManagerAPI {
|
|||||||
final GithubAPI _githubAPI = GithubAPI();
|
final GithubAPI _githubAPI = GithubAPI();
|
||||||
final RootAPI _rootAPI = RootAPI();
|
final RootAPI _rootAPI = RootAPI();
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
late List<RepositoryCommit> _commits = [];
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
|
_commits = (await _githubAPI.getCommits(ghOrg, patchesRepo)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadPatches(String extension) async {
|
Future<File?> downloadPatches(String extension) async {
|
||||||
@ -71,85 +74,58 @@ class ManagerAPI {
|
|||||||
setPatchedApps(patchedApps);
|
setPatchedApps(patchedApps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveApp(
|
|
||||||
ApplicationWithIcon application,
|
|
||||||
bool isRooted,
|
|
||||||
bool isFromStorage,
|
|
||||||
) {
|
|
||||||
savePatchedApp(
|
|
||||||
PatchedApplication(
|
|
||||||
name: application.appName,
|
|
||||||
packageName: application.packageName,
|
|
||||||
version: application.versionName!,
|
|
||||||
apkFilePath: application.apkFilePath,
|
|
||||||
icon: application.icon,
|
|
||||||
patchDate: DateTime.now(),
|
|
||||||
isRooted: isRooted,
|
|
||||||
isFromStorage: isFromStorage,
|
|
||||||
appliedPatches: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> reAssessSavedApps() async {
|
Future<void> reAssessSavedApps() async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
|
||||||
bool isRoot = isRooted() ?? false;
|
bool isRoot = isRooted() ?? false;
|
||||||
|
List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
List<PatchedApplication> toRemove = [];
|
List<PatchedApplication> toRemove = [];
|
||||||
for (PatchedApplication app in patchedApps) {
|
for (PatchedApplication app in patchedApps) {
|
||||||
bool existsRoot = false;
|
bool isRemove = await isAppUninstalled(app, isRoot);
|
||||||
if (isRoot) {
|
if (isRemove) {
|
||||||
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
|
||||||
}
|
|
||||||
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
|
||||||
if (!existsRoot && !existsNonRoot) {
|
|
||||||
toRemove.add(app);
|
toRemove.add(app);
|
||||||
} else if (existsNonRoot) {
|
} else {
|
||||||
ApplicationWithIcon? application =
|
List<String> newChangelog = getAppChangelog(
|
||||||
await DeviceApps.getApp(app.packageName, true)
|
app.packageName,
|
||||||
as ApplicationWithIcon?;
|
app.patchDate,
|
||||||
if (application != null) {
|
);
|
||||||
int savedVersionInt =
|
if (newChangelog.isNotEmpty) {
|
||||||
int.parse(app.version.replaceAll(RegExp('[^0-9]'), ''));
|
app.changelog = newChangelog;
|
||||||
int currentVersionInt = int.parse(
|
app.hasUpdates = true;
|
||||||
application.versionName!.replaceAll(RegExp('[^0-9]'), ''));
|
} else {
|
||||||
if (savedVersionInt < currentVersionInt) {
|
app.hasUpdates = false;
|
||||||
toRemove.add(app);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
patchedApps.removeWhere((a) => toRemove.contains(a));
|
patchedApps.removeWhere((a) => toRemove.contains(a));
|
||||||
setPatchedApps(patchedApps);
|
setPatchedApps(patchedApps);
|
||||||
List<String> apps = await _rootAPI.getInstalledApps();
|
}
|
||||||
for (String packageName in apps) {
|
|
||||||
if (!patchedApps.any((a) => a.packageName == packageName)) {
|
Future<bool> isAppUninstalled(PatchedApplication app, bool isRoot) async {
|
||||||
ApplicationWithIcon? application =
|
bool existsRoot = false;
|
||||||
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
if (isRoot) {
|
||||||
if (application != null) {
|
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
||||||
saveApp(application, true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
||||||
|
return !existsRoot && !existsNonRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasAppUpdates(String packageName) async {
|
List<String> getAppChangelog(String packageName, DateTime patchedDate) {
|
||||||
// TODO: get status based on last update time on the folder of this app?
|
List<String> newCommits = _commits
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> getAppChangelog(
|
|
||||||
String packageName,
|
|
||||||
DateTime lastUpdated,
|
|
||||||
) async {
|
|
||||||
return (await _githubAPI.getCommits(ghOrg, patchesRepo))
|
|
||||||
.where((c) =>
|
.where((c) =>
|
||||||
c.commit != null &&
|
c.commit != null &&
|
||||||
c.commit!.message != null &&
|
c.commit!.message != null &&
|
||||||
!c.commit!.message!.startsWith('chore') &&
|
|
||||||
c.commit!.author != null &&
|
c.commit!.author != null &&
|
||||||
c.commit!.author!.date != null)
|
c.commit!.author!.date != null &&
|
||||||
.map((c) => ' - ${c.commit!.message!}')
|
c.commit!.author!.date!.isAfter(patchedDate))
|
||||||
.toList()
|
.map((c) => c.commit!.message!)
|
||||||
.sublist(0, 3);
|
.toList();
|
||||||
|
if (newCommits.isNotEmpty) {
|
||||||
|
int firstChore = newCommits.indexWhere((c) => c.startsWith('chore'));
|
||||||
|
int secondChore =
|
||||||
|
newCommits.indexWhere((c) => c.startsWith('chore'), firstChore + 1);
|
||||||
|
if (firstChore >= 0 && secondChore > firstChore) {
|
||||||
|
return newCommits.sublist(firstChore + 1, secondChore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,6 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
icon: application.icon,
|
icon: application.icon,
|
||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
isRooted: _isRooted,
|
isRooted: _isRooted,
|
||||||
isFromStorage: false,
|
|
||||||
appliedPatches: [],
|
|
||||||
);
|
);
|
||||||
locator<PatcherViewModel>().selectedPatches.clear();
|
locator<PatcherViewModel>().selectedPatches.clear();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
locator<PatcherViewModel>().notifyListeners();
|
||||||
@ -63,7 +61,6 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
isRooted: _isRooted,
|
isRooted: _isRooted,
|
||||||
isFromStorage: true,
|
isFromStorage: true,
|
||||||
appliedPatches: [],
|
|
||||||
);
|
);
|
||||||
locator<PatcherViewModel>().selectedPatches.clear();
|
locator<PatcherViewModel>().selectedPatches.clear();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
locator<PatcherViewModel>().notifyListeners();
|
||||||
|
@ -111,8 +111,8 @@ class HomeView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
model.showUpdatableApps
|
model.showUpdatableApps
|
||||||
? AvailableUpdatesCard()
|
? const AvailableUpdatesCard()
|
||||||
: InstalledAppsCard()
|
: const InstalledAppsCard()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -22,8 +22,11 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
bool showUpdatableApps = true;
|
bool showUpdatableApps = true;
|
||||||
|
List<PatchedApplication> patchedInstalledApps = [];
|
||||||
|
List<PatchedApplication> patchedUpdatableApps = [];
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
|
await _getPatchedApps();
|
||||||
await _patcherAPI.loadPatches();
|
await _patcherAPI.loadPatches();
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
const InitializationSettings(
|
const InitializationSettings(
|
||||||
@ -31,6 +34,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
),
|
),
|
||||||
onSelectNotification: (p) => DeviceApps.openApp('app.revanced.manager'),
|
onSelectNotification: (p) => DeviceApps.openApp('app.revanced.manager'),
|
||||||
);
|
);
|
||||||
|
_managerAPI.reAssessSavedApps().then((_) => notifyListeners());
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleUpdatableApps(bool value) {
|
void toggleUpdatableApps(bool value) {
|
||||||
@ -46,17 +50,16 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
locator<MainViewModel>().setIndex(1);
|
locator<MainViewModel>().setIndex(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<PatchedApplication>> getPatchedApps(bool isUpdatable) async {
|
Future<void> _getPatchedApps() async {
|
||||||
await _managerAPI.reAssessSavedApps();
|
patchedInstalledApps = _managerAPI
|
||||||
List<PatchedApplication> list = [];
|
.getPatchedApps()
|
||||||
List<PatchedApplication> patchedApps = _managerAPI.getPatchedApps();
|
.where((app) => app.hasUpdates == false)
|
||||||
for (PatchedApplication app in patchedApps) {
|
.toList();
|
||||||
bool hasUpdates = await _managerAPI.hasAppUpdates(app.packageName);
|
patchedUpdatableApps = _managerAPI
|
||||||
if (hasUpdates == isUpdatable) {
|
.getPatchedApps()
|
||||||
list.add(app);
|
.where((app) => app.hasUpdates == true)
|
||||||
}
|
.toList();
|
||||||
}
|
notifyListeners();
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasManagerUpdates() async {
|
Future<bool> hasManagerUpdates() async {
|
||||||
|
@ -70,10 +70,10 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
void update(double value, String header, String log) {
|
void update(double value, String header, String log) {
|
||||||
progress = value;
|
progress = value;
|
||||||
isInstalled = false;
|
|
||||||
isPatching = progress == 1.0 ? false : true;
|
isPatching = progress == 1.0 ? false : true;
|
||||||
if (progress == 0.0) {
|
if (progress == 0.0) {
|
||||||
logs = '';
|
logs = '';
|
||||||
|
isInstalled = false;
|
||||||
}
|
}
|
||||||
if (header.isNotEmpty) {
|
if (header.isNotEmpty) {
|
||||||
headerLogs = header;
|
headerLogs = header;
|
||||||
@ -148,9 +148,9 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
);
|
);
|
||||||
isInstalled = await _patcherAPI.installPatchedFile(_app!);
|
isInstalled = await _patcherAPI.installPatchedFile(_app!);
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
update(1.0, 'Installed!', 'Installed');
|
update(1.0, 'Installed!', 'Installed!');
|
||||||
_app!.patchDate = DateTime.now();
|
_app!.patchDate = DateTime.now();
|
||||||
_app!.appliedPatches.addAll(_patches.map((p) => p.name).toList());
|
_app!.appliedPatches = _patches.map((p) => p.name).toList();
|
||||||
_managerAPI.savePatchedApp(_app!);
|
_managerAPI.savePatchedApp(_app!);
|
||||||
} else {
|
} else {
|
||||||
update(1.0, 'Aborting...', 'An error occurred! Aborting');
|
update(1.0, 'Aborting...', 'An error occurred! Aborting');
|
||||||
|
@ -1,54 +1,32 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
|
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
|
||||||
|
|
||||||
class AvailableUpdatesCard extends StatelessWidget {
|
class AvailableUpdatesCard extends StatelessWidget {
|
||||||
AvailableUpdatesCard({
|
const AvailableUpdatesCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return ListView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
shrinkWrap: true,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
FutureBuilder<List<PatchedApplication>>(
|
children: locator<HomeViewModel>()
|
||||||
future: locator<HomeViewModel>().getPatchedApps(true),
|
.patchedUpdatableApps
|
||||||
builder: (context, snapshot) => snapshot.hasData &&
|
.map((app) => ApplicationItem(
|
||||||
snapshot.data!.isNotEmpty
|
icon: app.icon,
|
||||||
? ListView.builder(
|
name: app.name,
|
||||||
shrinkWrap: true,
|
patchDate: app.patchDate,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
changelog: app.changelog,
|
||||||
padding: EdgeInsets.zero,
|
isUpdatableApp: true,
|
||||||
itemCount: snapshot.data!.length,
|
onPressed: () => locator<HomeViewModel>().navigateToPatcher(
|
||||||
itemBuilder: (context, index) => FutureBuilder<List<String>>(
|
app,
|
||||||
future: _managerAPI.getAppChangelog(
|
),
|
||||||
snapshot.data![index].packageName,
|
))
|
||||||
snapshot.data![index].patchDate,
|
.toList(),
|
||||||
),
|
|
||||||
initialData: List.empty(),
|
|
||||||
builder: (context, snapshot2) => ApplicationItem(
|
|
||||||
icon: snapshot.data![index].icon,
|
|
||||||
name: snapshot.data![index].name,
|
|
||||||
patchDate: snapshot.data![index].patchDate,
|
|
||||||
changelog: '${snapshot2.data!.join('\n')}\n...',
|
|
||||||
isUpdatableApp: true,
|
|
||||||
onPressed: () =>
|
|
||||||
locator<HomeViewModel>().navigateToPatcher(
|
|
||||||
snapshot.data![index],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Container(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,31 @@
|
|||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
|
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
|
||||||
|
|
||||||
class InstalledAppsCard extends StatelessWidget {
|
class InstalledAppsCard extends StatelessWidget {
|
||||||
InstalledAppsCard({
|
const InstalledAppsCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return ListView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
shrinkWrap: true,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
FutureBuilder<List<PatchedApplication>>(
|
children: locator<HomeViewModel>()
|
||||||
future: locator<HomeViewModel>().getPatchedApps(false),
|
.patchedInstalledApps
|
||||||
builder: (context, snapshot) => snapshot.hasData &&
|
.map((app) => ApplicationItem(
|
||||||
snapshot.data!.isNotEmpty
|
icon: app.icon,
|
||||||
? ListView.builder(
|
name: app.name,
|
||||||
shrinkWrap: true,
|
patchDate: app.patchDate,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
changelog: app.changelog,
|
||||||
padding: EdgeInsets.zero,
|
isUpdatableApp: false,
|
||||||
itemCount: snapshot.data!.length,
|
onPressed: () => DeviceApps.openApp(app.packageName),
|
||||||
itemBuilder: (context, index) => FutureBuilder<List<String>>(
|
))
|
||||||
future: _managerAPI.getAppChangelog(
|
.toList(),
|
||||||
snapshot.data![index].packageName,
|
|
||||||
snapshot.data![index].patchDate,
|
|
||||||
),
|
|
||||||
initialData: const ['Loading'],
|
|
||||||
builder: (context, snapshot2) => ApplicationItem(
|
|
||||||
icon: snapshot.data![index].icon,
|
|
||||||
name: snapshot.data![index].name,
|
|
||||||
patchDate: snapshot.data![index].patchDate,
|
|
||||||
changelog: '${snapshot2.data!.join('\n')}\n(...)',
|
|
||||||
isUpdatableApp: false,
|
|
||||||
onPressed: () => DeviceApps.openApp(
|
|
||||||
snapshot.data![index].packageName,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Container(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class ApplicationItem extends StatelessWidget {
|
|||||||
final Uint8List icon;
|
final Uint8List icon;
|
||||||
final String name;
|
final String name;
|
||||||
final DateTime patchDate;
|
final DateTime patchDate;
|
||||||
final String changelog;
|
final List<String> changelog;
|
||||||
final bool isUpdatableApp;
|
final bool isUpdatableApp;
|
||||||
final Function() onPressed;
|
final Function() onPressed;
|
||||||
|
|
||||||
@ -103,8 +103,9 @@ class ApplicationItem extends StatelessWidget {
|
|||||||
style: kRobotoTextStyle.copyWith(fontWeight: FontWeight.w700),
|
style: kRobotoTextStyle.copyWith(fontWeight: FontWeight.w700),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
changelog,
|
' - ${changelog.join('\n- ')}',
|
||||||
style: kRobotoTextStyle,
|
style: kRobotoTextStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user