forked from firka/firka
android: compress flutter resources
Flutter allows for replacing the default asset bundle with a custom one, so we can just compress the resources with brotli / gzip or keep it as is (which ever one is smaller), and then decompress it inside our custom asset bundle class.
This commit is contained in:
@@ -4,6 +4,7 @@ import java.security.MessageDigest
|
||||
import java.util.Properties
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import java.util.zip.ZipOutputStream.DEFLATED
|
||||
@@ -206,11 +207,13 @@ fun transformAndSignApk(apkDir: File, name: String, debug: Boolean) {
|
||||
|
||||
fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
|
||||
val tempDir = File(project.buildDir, "tmp/apk-transform")
|
||||
val cacheDir = File(project.buildDir, "apk-transform-cache")
|
||||
val cacheDir = File(project.buildDir, "cache")
|
||||
val optipngCacheDir = File(cacheDir, "optipng")
|
||||
val assetCompressionDir = File(cacheDir, "assets")
|
||||
tempDir.deleteRecursively()
|
||||
tempDir.mkdirs()
|
||||
if (!optipngCacheDir.exists()) optipngCacheDir.mkdirs()
|
||||
if (!assetCompressionDir.exists()) assetCompressionDir.mkdirs()
|
||||
|
||||
val brotli = findToolInPath("brotli")
|
||||
?: throw Exception("Brotli not found in path")
|
||||
@@ -264,30 +267,159 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
|
||||
val zos = ZipOutputStream(output.outputStream())
|
||||
|
||||
val coreCount = Runtime.getRuntime().availableProcessors()
|
||||
val flutterResources = tempDir.walkTopDown().filter{f -> f.absolutePath.contains("flutter_assets")}
|
||||
val pngFiles = tempDir.walkTopDown().filter{f -> f.name.endsWith(".png")}
|
||||
|
||||
if (compressionLevel == "Z" && optipng != null) {
|
||||
val assetIndex = mutableMapOf<String, String>()
|
||||
val indexReadWriteLock = ReentrantReadWriteLock()
|
||||
|
||||
if (compressionLevel == "Z") {
|
||||
if (optipng != null) {
|
||||
val executor = Executors.newFixedThreadPool(coreCount)
|
||||
val futures = mutableListOf<Future<*>>()
|
||||
|
||||
pngFiles.forEach { pngFile ->
|
||||
val cacheFile = File(optipngCacheDir, pngFile.sha256())
|
||||
|
||||
if (cacheFile.exists()) {
|
||||
cacheFile.copyTo(pngFile, true)
|
||||
} else {
|
||||
val future = executor.submit {
|
||||
exec {
|
||||
commandLine(
|
||||
optipng,
|
||||
"-zm", "9",
|
||||
"-zw", "32k",
|
||||
"-o9",
|
||||
pngFile.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
pngFile.copyTo(cacheFile, true)
|
||||
}
|
||||
|
||||
futures.add(future)
|
||||
}
|
||||
}
|
||||
|
||||
futures.forEach { it.get() }
|
||||
executor.shutdown()
|
||||
}
|
||||
|
||||
val executor = Executors.newFixedThreadPool(coreCount)
|
||||
val futures = mutableListOf<Future<*>>()
|
||||
|
||||
pngFiles.forEach { pngFile ->
|
||||
val cacheFile = File(optipngCacheDir, pngFile.sha256())
|
||||
val blacklist = listOf(
|
||||
// "AssetManifest.bin",
|
||||
"AssetManifest.json",
|
||||
"FontManifest.json",
|
||||
"isolate_snapshot_data",
|
||||
"kernel_blob.bin",
|
||||
"NativeAssetsManifest.json",
|
||||
"NOTICES.Z",
|
||||
"vm_snapshot_data",
|
||||
"fonts",
|
||||
"shaders"
|
||||
)
|
||||
|
||||
if (cacheFile.exists()) {
|
||||
cacheFile.copyTo(pngFile, true)
|
||||
flutterResources.forEach { f ->
|
||||
val relName = f.absolutePath.substring(topDirL).replace("\\", "/")
|
||||
if (f.isDirectory) return@forEach
|
||||
|
||||
val cacheFileRaw = File(assetCompressionDir, f.sha256()+".r")
|
||||
val cacheFileGz = File(assetCompressionDir, f.sha256()+".gz")
|
||||
val cacheFileBr = File(assetCompressionDir, f.sha256()+".br")
|
||||
|
||||
if (cacheFileRaw.exists() || cacheFileGz.exists() || cacheFileBr.exists()) {
|
||||
if (cacheFileRaw.exists()) {
|
||||
cacheFileRaw.copyTo(f, true)
|
||||
|
||||
indexReadWriteLock.writeLock().lock()
|
||||
assetIndex[relName] = "r"
|
||||
indexReadWriteLock.writeLock().unlock()
|
||||
} else if (cacheFileGz.exists()) {
|
||||
cacheFileGz.copyTo(f, true)
|
||||
|
||||
indexReadWriteLock.writeLock().lock()
|
||||
assetIndex[relName] = "g"
|
||||
indexReadWriteLock.writeLock().unlock()
|
||||
} else {
|
||||
cacheFileBr.copyTo(f, true)
|
||||
|
||||
indexReadWriteLock.writeLock().lock()
|
||||
assetIndex[relName] = "b"
|
||||
indexReadWriteLock.writeLock().unlock()
|
||||
}
|
||||
} else {
|
||||
val future = executor.submit {
|
||||
exec {
|
||||
commandLine(
|
||||
optipng,
|
||||
"-zm", "9",
|
||||
"-zw", "32k",
|
||||
"-o9",
|
||||
pngFile.absolutePath
|
||||
)
|
||||
val brTmp = File(f.absolutePath + ".br.tmp")
|
||||
val gzTmp = File(f.absolutePath + ".gz.tmp")
|
||||
|
||||
var blacklisted = false
|
||||
for (f in blacklist) {
|
||||
if (relName.contains(f)) {
|
||||
blacklisted = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pngFile.copyTo(cacheFile, true)
|
||||
if (!blacklisted) {
|
||||
println("$relName: Testing with brotli")
|
||||
exec {
|
||||
commandLine(
|
||||
brotli,
|
||||
"-$compressionLevel",
|
||||
f.absolutePath,
|
||||
"-o", brTmp.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
println("$relName: Testing with gzip")
|
||||
ant.invokeMethod(
|
||||
"gzip", mapOf(
|
||||
"src" to f.absolutePath,
|
||||
"destfile" to gzTmp.absolutePath,
|
||||
)
|
||||
)
|
||||
|
||||
println("$brTmp: ${brTmp.length()}")
|
||||
println("$gzTmp: ${gzTmp.length()}")
|
||||
if (f.length() < gzTmp.length() && f.length() < brTmp.length()) {
|
||||
println("$relName: Raw file wins")
|
||||
|
||||
f.copyTo(cacheFileRaw, true)
|
||||
|
||||
indexReadWriteLock.writeLock().lock()
|
||||
assetIndex[relName] = "r"
|
||||
indexReadWriteLock.writeLock().unlock()
|
||||
} else {
|
||||
if (brTmp.length() < gzTmp.length()) {
|
||||
println("$relName: Brotli wins")
|
||||
|
||||
f.delete()
|
||||
brTmp.copyTo(f, true)
|
||||
brTmp.copyTo(cacheFileBr, true)
|
||||
|
||||
indexReadWriteLock.writeLock().lock()
|
||||
assetIndex[relName] = "b"
|
||||
indexReadWriteLock.writeLock().unlock()
|
||||
} else {
|
||||
println("$relName: Gzip wins")
|
||||
|
||||
f.delete()
|
||||
gzTmp.copyTo(f, true)
|
||||
gzTmp.copyTo(cacheFileGz, true)
|
||||
|
||||
indexReadWriteLock.writeLock().lock()
|
||||
assetIndex[relName] = "g"
|
||||
indexReadWriteLock.writeLock().unlock()
|
||||
}
|
||||
}
|
||||
|
||||
brTmp.delete()
|
||||
gzTmp.delete()
|
||||
}
|
||||
}
|
||||
|
||||
futures.add(future)
|
||||
@@ -304,6 +436,12 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
|
||||
var relName = f.absolutePath.substring(topDirL).replace("\\", "/")
|
||||
if (f.isDirectory && !relName.endsWith("/")) relName += "/"
|
||||
|
||||
if (compressionLevel == "Z") {
|
||||
if (relName == "assets/flutter_assets/assets/firka.i") return@forEach
|
||||
}
|
||||
|
||||
println(relName)
|
||||
|
||||
val compress = !relName.endsWith(".so") && !relName.endsWith(".arsc")
|
||||
zos.setMethod(if (compress) { DEFLATED } else { STORED })
|
||||
val entry = ZipEntry(relName)
|
||||
@@ -317,13 +455,34 @@ fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
|
||||
}
|
||||
zos.closeEntry()
|
||||
}
|
||||
zos.close()
|
||||
if (compressionLevel == "Z") {
|
||||
zos.setMethod(DEFLATED)
|
||||
zos.putNextEntry(ZipEntry("assets/flutter_assets/assets/firka.i"))
|
||||
|
||||
ant.invokeMethod("zip", mapOf(
|
||||
"destfile" to output.absolutePath,
|
||||
"basedir" to tempDir.absolutePath,
|
||||
"level" to 0
|
||||
))
|
||||
val indexUncompressed = File(tempDir, "index.json")
|
||||
indexReadWriteLock.readLock().lock()
|
||||
val json = groovy.json.JsonBuilder(assetIndex)
|
||||
indexReadWriteLock.readLock().unlock()
|
||||
indexUncompressed.writeText(json.toString())
|
||||
|
||||
val indexCompressed = File(tempDir, "index.json.br")
|
||||
|
||||
exec {
|
||||
commandLine(
|
||||
brotli,
|
||||
"-$compressionLevel",
|
||||
indexUncompressed.absolutePath,
|
||||
"-o", indexCompressed.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
zos.write(indexCompressed.readBytes())
|
||||
indexUncompressed.delete()
|
||||
indexCompressed.delete()
|
||||
|
||||
zos.closeEntry()
|
||||
}
|
||||
zos.close()
|
||||
|
||||
tempDir.deleteRecursively()
|
||||
println("APK transformed successfully")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 146 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 402 KiB |
0
firka/assets/firka.i
Normal file
0
firka/assets/firka.i
Normal file
55
firka/lib/helpers/firka_bundle.dart
Normal file
55
firka/lib/helpers/firka_bundle.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:brotli/brotli.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class FirkaBundle extends CachingAssetBundle {
|
||||
final bool _compressedBundle = !kDebugMode && Platform.isAndroid;
|
||||
|
||||
Map<String, dynamic>? index;
|
||||
|
||||
Future<Map<String, dynamic>> loadIndex() async {
|
||||
var indexBrotli = await rootBundle.load("assets/firka.i");
|
||||
var indexStr = brotli.decodeToString(indexBrotli.buffer.asInt8List());
|
||||
|
||||
return Future.value(jsonDecode(indexStr));
|
||||
}
|
||||
|
||||
ByteData decode(Codec<List<int>, List<int>> codec, ByteData data) {
|
||||
var dec = codec.decode(data.buffer.asInt8List());
|
||||
var b = ByteData(dec.length);
|
||||
var l = b.buffer.asInt8List();
|
||||
|
||||
for (var i = 0; i < dec.length; i++) {
|
||||
l[i] = dec[i];
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) async {
|
||||
if (!_compressedBundle) {
|
||||
return rootBundle.load(key);
|
||||
} else {
|
||||
index ??= await loadIndex();
|
||||
|
||||
final gzip = GZipCodec();
|
||||
|
||||
debugPrint("assets/flutter_assets/$key");
|
||||
switch (index!["assets/flutter_assets/$key"]!) {
|
||||
case "b": // brotli
|
||||
return decode(brotli, await rootBundle.load(key));
|
||||
case "g": // gzip
|
||||
return decode(gzip, await rootBundle.load(key));
|
||||
case "r": // raw
|
||||
return rootBundle.load(key);
|
||||
default:
|
||||
throw "Unknown file format: ${index![key]!}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:firka/helpers/db/models/timetable_cache_model.dart';
|
||||
import 'package:firka/helpers/db/models/token_model.dart';
|
||||
import 'package:firka/helpers/db/widget.dart';
|
||||
import 'package:firka/helpers/extensions.dart';
|
||||
import 'package:firka/helpers/firka_bundle.dart';
|
||||
import 'package:firka/helpers/settings/setting.dart';
|
||||
import 'package:firka/l10n/app_localizations_hu.dart';
|
||||
import 'package:firka/ui/model/style.dart';
|
||||
@@ -180,14 +181,16 @@ class InitializationScreen extends StatelessWidget {
|
||||
// Handle initialization error
|
||||
return MaterialApp(
|
||||
key: ValueKey('errorPage'),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Error initializing app: ${snapshot.error}',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
home: DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Error initializing app: ${snapshot.error}',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,30 +249,39 @@ class InitializationScreen extends StatelessWidget {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: screen,
|
||||
home: DefaultAssetBundle(bundle: FirkaBundle(), child: screen),
|
||||
routes: {
|
||||
'/login': (context) => LoginScreen(
|
||||
initData,
|
||||
key: ValueKey('loginScreen'),
|
||||
'/login': (context) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: LoginScreen(
|
||||
initData,
|
||||
key: ValueKey('loginScreen'),
|
||||
),
|
||||
),
|
||||
'/debug': (context) => DebugScreen(
|
||||
initData,
|
||||
key: ValueKey('debugScreen'),
|
||||
'/debug': (context) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: DebugScreen(
|
||||
initData,
|
||||
key: ValueKey('debugScreen'),
|
||||
),
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
color: const Color(0xFF7CA021),
|
||||
)
|
||||
],
|
||||
home: DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
color: const Color(0xFF7CA021),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:firka/ui/model/style.dart';
|
||||
import 'package:firka/ui/phone/screens/settings/settings_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../helpers/firka_bundle.dart';
|
||||
import '../../screens/debug/debug_screen.dart';
|
||||
|
||||
void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
@@ -44,7 +45,9 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DebugScreen(data)))
|
||||
builder: (context) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: DebugScreen(data))))
|
||||
},
|
||||
child: FirkaCard(
|
||||
left: [Text('Debug screen')],
|
||||
@@ -57,8 +60,10 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
SettingsScreen(data, data.settings.items)));
|
||||
builder: (context) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: SettingsScreen(
|
||||
data, data.settings.items))));
|
||||
},
|
||||
child: FirkaCard(
|
||||
left: [Text('Settings')],
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import '../../../../helpers/debug_helper.dart';
|
||||
import '../../../../helpers/firka_bundle.dart';
|
||||
import '../../../widget/firka_icon.dart';
|
||||
|
||||
class DebugScreen extends StatefulWidget {
|
||||
@@ -217,7 +218,9 @@ class _DebugScreen extends State<DebugScreen> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LoginScreen(widget.data)));
|
||||
builder: (context) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: LoginScreen(widget.data))));
|
||||
},
|
||||
child: const Text('wipe users'),
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:firka/ui/widget/firka_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../../../helpers/firka_bundle.dart';
|
||||
import '../../../../helpers/settings/setting.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
@@ -52,8 +53,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> createWidgetTree(Iterable<SettingsItem> items,
|
||||
SettingsStore settings) {
|
||||
List<Widget> createWidgetTree(
|
||||
Iterable<SettingsItem> items, SettingsStore settings) {
|
||||
var widgets = List<Widget>.empty(growable: true);
|
||||
|
||||
for (var item in items) {
|
||||
@@ -83,7 +84,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
widgets.add(Text(
|
||||
item.title,
|
||||
style:
|
||||
appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary),
|
||||
appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary),
|
||||
));
|
||||
|
||||
continue;
|
||||
@@ -109,8 +110,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
SettingsScreen(widget.data, item.children)));
|
||||
builder: (context) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: SettingsScreen(widget.data, item.children))));
|
||||
},
|
||||
child: FirkaCard(left: cardWidgets),
|
||||
));
|
||||
@@ -188,9 +190,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Checkbox(
|
||||
value: true,
|
||||
fillColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return appStyle.colors.secondary;
|
||||
}),
|
||||
(Set<WidgetState> states) {
|
||||
return appStyle.colors.secondary;
|
||||
}),
|
||||
onChanged: (_) async {
|
||||
setState(() {
|
||||
item.activeIndex = i;
|
||||
@@ -235,10 +237,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
fit: BoxFit.cover),
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
width: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
@@ -247,7 +246,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(16.0)),
|
||||
const BorderRadius.all(Radius.circular(16.0)),
|
||||
child: Image.asset(
|
||||
"assets/images/icons/$activeIcon.png",
|
||||
width: 74,
|
||||
@@ -281,38 +280,38 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
GestureDetector(
|
||||
child: active
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: appStyle.colors.accent,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(12.0)),
|
||||
child: Image.asset(
|
||||
"assets/images/icons/$icon.png",
|
||||
width: 48,
|
||||
height: 48,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
decoration: BoxDecoration(
|
||||
color: appStyle.colors.accent,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(12.0)),
|
||||
child: Image.asset(
|
||||
"assets/images/icons/$icon.png",
|
||||
width: 48,
|
||||
height: 48,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: appStyle.colors.accent,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(16.0)),
|
||||
child: Image.asset(
|
||||
"assets/images/icons/$icon.png",
|
||||
width: 54,
|
||||
height: 54,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: appStyle.colors.accent,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(16.0)),
|
||||
child: Image.asset(
|
||||
"assets/images/icons/$icon.png",
|
||||
width: 54,
|
||||
height: 54,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (settingAppIcon) return;
|
||||
|
||||
@@ -333,7 +332,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
pWidgets.add(Text(
|
||||
group,
|
||||
style:
|
||||
appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary),
|
||||
appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary),
|
||||
));
|
||||
pWidgets.add(SizedBox(height: 12));
|
||||
pWidgets.add(SizedBox(
|
||||
@@ -347,15 +346,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
|
||||
widgets.add(SizedBox(
|
||||
height: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height / 1.7,
|
||||
height: MediaQuery.of(context).size.height / 1.7,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: pWidgets,
|
||||
)),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: pWidgets,
|
||||
)),
|
||||
));
|
||||
|
||||
widgets.add(Row(
|
||||
@@ -421,10 +417,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
backgroundColor: appStyle.colors.background,
|
||||
body: SafeArea(
|
||||
child: SizedBox(
|
||||
height: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
@@ -452,15 +445,11 @@ void showSetDoubleSheet(BuildContext context, SettingsDouble setting,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: appStyle.colors.a15p,
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.height * 0.13,
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.13,
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, setState) =>
|
||||
Stack(
|
||||
builder: (BuildContext context, setState) => Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
@@ -475,7 +464,7 @@ void showSetDoubleSheet(BuildContext context, SettingsDouble setting,
|
||||
decoration: BoxDecoration(
|
||||
color: appStyle.colors.card,
|
||||
borderRadius:
|
||||
BorderRadius.vertical(top: Radius.circular(16)),
|
||||
BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
@@ -484,9 +473,9 @@ void showSetDoubleSheet(BuildContext context, SettingsDouble setting,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
setting.title,
|
||||
style: appStyle.fonts.B_14R,
|
||||
)),
|
||||
setting.title,
|
||||
style: appStyle.fonts.B_14R,
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 40),
|
||||
|
||||
@@ -59,6 +59,7 @@ dependencies:
|
||||
flutter_arc_text: ^0.6.0
|
||||
flutter_svg: ^1.1.6
|
||||
home_widget: ^0.8.0
|
||||
brotli: ^0.6.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -88,6 +89,7 @@ flutter:
|
||||
- assets/images/icons/
|
||||
- assets/images/background.png
|
||||
- assets/majesticons/
|
||||
- assets/firka.i
|
||||
|
||||
fonts:
|
||||
- family: Montserrat
|
||||
|
||||
Reference in New Issue
Block a user