Skip to content

Commit

Permalink
(#1) Fix painting memory gap and use [ManageExternalDirectory] permis…
Browse files Browse the repository at this point in the history
…sion to read and write on root Android folder (used to export the Apk)
  • Loading branch information
alexrintt committed Oct 25, 2021
1 parent 5bdfae4 commit a9e1548
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 81 deletions.
20 changes: 16 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
compileSdkVersion 31

Expand All @@ -42,19 +48,25 @@ android {
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.lakscastro.kanade"
minSdkVersion 16
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
</queries>

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<application
Expand Down
6 changes: 4 additions & 2 deletions lib/stores/device_apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,18 @@ class DeviceAppsStore extends ChangeNotifier {

final apkFilename = '${package.appName} ${package.packageName} $id.apk';

await Permission.storage.request();
await Permission.manageExternalStorage.request();

final status = await Permission.storage.status;
final status = await Permission.manageExternalStorage.status;

if (status.isDenied || status.isPermanentlyDenied) {
return const ApkExtraction(null, Result.permissionDenied);
} else if (status.isRestricted || status.isLimited) {
return const ApkExtraction(null, Result.permissionRestricted);
} else if (status.isGranted) {
const kRootFolder = 'Kanade';

/// TODO(@LaksCastro): Find or create a package that returns the Android root folder
final appDir = Directory('storage/emulated/0/$kRootFolder');

if (!appDir.existsSync()) {
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/loading.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class _LoadingState extends State<Loading> {
return Positioned.fill(
child: DottedBackground(
color: kWhite10,
size: k5dp,
size: k3dp,
),
);
}
Expand Down
107 changes: 38 additions & 69 deletions lib/widgets/package_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import 'package:kanade/constants/app_spacing.dart';
import 'package:kanade/icons/pixel_art_icons.dart';
import 'package:kanade/stores/contextual_menu.dart';
import 'package:kanade/stores/device_apps.dart';
import 'package:kanade/widgets/dotted_background.dart';
import 'package:kanade/widgets/multi_animated_builder.dart';
import 'package:kanade/widgets/toast.dart';

import 'app_icon_button.dart';

class PackageTile extends StatefulWidget {
final Application package;
final bool isSelected;
final bool showCheckbox;
final VoidCallback onPressed;
final VoidCallback onLongPress;

const PackageTile(
this.package, {
Key? key,
required this.isSelected,
required this.showCheckbox,
required this.onPressed,
required this.onLongPress,
}) : super(key: key);

@override
Expand All @@ -34,52 +39,25 @@ class _PackageTileState extends State<PackageTile>
Uint8List? get _icon =>
_hasIcon ? (widget.package as ApplicationWithIcon).icon : null;

bool get _isSelected =>
menuStore.context.isSelection && store.isSelected(widget.package);
bool get _isSelected => widget.isSelected;

void _onLongPress() {
menuStore.showSelectionMenu();
store.toggleSelect(widget.package);
}

void _onPressed() async {
if (menuStore.context.isSelection) {
store.toggleSelect(widget.package);
} else {
final extraction = await store.extractApk(widget.package);

if (extraction.result.success) {
showToast(context, 'Extracted to ${extraction.apk!.path}');
} else if (extraction.result.permissionWasDenied) {
showToast(context, 'Permission denied');
} else if (extraction.result.restrictedPermission) {
showToast(context, 'Permission restricted by Android');
}
Widget? _buildTrailing() {
Widget? child;

if (widget.showCheckbox) {
child = AppIconButton(
onTap: () => store.toggleSelect(widget.package),
tooltip: 'Toggle Select',
icon: Icon(
_isSelected ? PixelArt.checkbox : PixelArt.checkbox_on,
),
);
}
}

Widget? _buildTrailing() {
return MultiAnimatedBuilder(
animations: [store, menuStore],
builder: (context, child) {
Widget? child;

if (menuStore.context.isSelection) {
child = AppIconButton(
onTap: () => store.toggleSelect(widget.package),
tooltip: 'Toggle Select',
icon: Icon(
_isSelected ? PixelArt.checkbox : PixelArt.checkbox_on,
),
);
}

return SizedBox(
width: _kLeadingSize.width,
height: _kLeadingSize.height,
child: child,
);
},
return SizedBox(
width: _kLeadingSize.width,
height: _kLeadingSize.height,
child: child,
);
}

Expand All @@ -95,12 +73,10 @@ class _PackageTileState extends State<PackageTile>
return SizedBox(
width: _kLeadingSize.width,
height: _kLeadingSize.height,
child: RepaintBoundary(
child: Center(
child: _hasIcon
? Image.memory(_icon!, gaplessPlayback: true)
: const Icon(PixelArt.android),
),
child: Center(
child: _hasIcon
? Image.memory(_icon!, gaplessPlayback: true)
: const Icon(PixelArt.android),
),
);
}
Expand Down Expand Up @@ -132,18 +108,15 @@ class _PackageTileState extends State<PackageTile>
}

Widget _buildInkEffectWrapper() {
return AnimatedBuilder(
animation: store,
builder: (context, child) => InkWell(
splashFactory: InkSplash.splashFactory,
onTap: _onPressed,
onLongPress: _onLongPress,
splashColor: kWhite03,
highlightColor: kWhite03,
child: DecoratedBox(
decoration: _createBoxDecoration(),
child: _buildListTile(),
),
return InkWell(
splashFactory: InkSplash.splashFactory,
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
splashColor: kWhite03,
highlightColor: kWhite03,
child: DecoratedBox(
decoration: _createBoxDecoration(),
child: _buildListTile(),
),
);
}
Expand Down Expand Up @@ -172,11 +145,7 @@ class _PackageTileState extends State<PackageTile>
color: kCardColor,
elevation: k1dp,
shadowColor: Theme.of(context).scaffoldBackgroundColor,
child: DottedBackground(
color: kWhite10,
size: k3dp,
child: _buildInkEffectWrapper(),
),
child: _buildInkEffectWrapper(),
),
);
}
Expand Down
37 changes: 34 additions & 3 deletions lib/widgets/packages_list.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import 'package:device_apps/device_apps.dart';
import 'package:flutter/cupertino.dart';
import 'package:kanade/constants/app_colors.dart';
import 'package:kanade/constants/app_spacing.dart';
import 'package:kanade/stores/contextual_menu.dart';
import 'package:kanade/stores/device_apps.dart';
import 'package:kanade/widgets/package_tile.dart';
import 'package:kanade/widgets/toast.dart';

import 'contextual_menu.dart';
import 'dotted_background.dart';
import 'multi_animated_builder.dart';

class PackagesList extends StatefulWidget {
const PackagesList({Key? key}) : super(key: key);
Expand All @@ -17,10 +20,31 @@ class PackagesList extends StatefulWidget {

class _PackagesListState extends State<PackagesList>
with DeviceAppsStoreConsumer, ContextualMenuStoreConsumer {
void _onLongPress(Application package) {
menuStore.showSelectionMenu();
store.toggleSelect(package);
}

void _onPressed(Application package) async {
if (menuStore.context.isSelection) {
store.toggleSelect(package);
} else {
final extraction = await store.extractApk(package);

if (extraction.result.success) {
showToast(context, 'Extracted to ${extraction.apk!.path}');
} else if (extraction.result.permissionWasDenied) {
showToast(context, 'Permission denied');
} else if (extraction.result.restrictedPermission) {
showToast(context, 'Permission restricted by Android');
}
}
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: store,
return MultiAnimatedBuilder(
animations: [store, menuStore],
builder: (context, child) => CustomScrollView(
slivers: [
const ContextualMenu(),
Expand All @@ -31,7 +55,14 @@ class _PackagesListState extends State<PackagesList>
(context, index) {
final current = store.displayableApps[index];

return PackageTile(current);
return PackageTile(
current,
showCheckbox: menuStore.context.isSelection,
onLongPress: () => _onLongPress(current),
onPressed: () => _onPressed(current),
isSelected: menuStore.context.isSelection &&
store.isSelected(current),
);
},
childCount: store.displayableApps.length,
),
Expand Down

0 comments on commit a9e1548

Please sign in to comment.