Skip to content

Commit

Permalink
Improve bottom bar and file downloads (#158)
Browse files Browse the repository at this point in the history
* feat: change bottom nav bar style to fixed

* refactor: migrate file store to null safety

* fix: illegal access firebase for auto roll call while feature not enabled

* refactor: migrate toast to null safety

* refactor: migrate permission util to null safety

* refactor: migrate file download to null safety, and add dialog while download complete

* refactor: migrade iplus file page to null safety

* chore: remove unneeded aos permissions
  • Loading branch information
Xanonymous-GitHub authored Jan 26, 2023
1 parent 8f7cae4 commit d504e08
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 202 deletions.
7 changes: 1 addition & 6 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<!-- Remove the REQUEST_INSTALL_PACKAGES permission since the `open_file` package has enabled this permission. -->
<!-- TODO: migrate to `open_file_safe` package or modify dependencies in `alice` -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove"/>
Expand All @@ -21,8 +17,7 @@
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
android:label="TAT"
tools:targetApi="m">
android:label="TAT">

<activity
android:name=".MainActivity"
Expand Down
155 changes: 81 additions & 74 deletions lib/src/file/file_download.dart
Original file line number Diff line number Diff line change
@@ -1,118 +1,125 @@
// TODO: remove sdk version selector after migrating to null-safety.
// @dart=2.10
// ignore_for_file: import_of_legacy_library_into_null_safe

import 'dart:convert';

import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:dio/dio.dart';
import 'package:flutter_app/debug/log/log.dart';
import 'package:flutter_app/src/connector/core/dio_connector.dart';
import 'package:flutter_app/src/notifications/notifications.dart';
import 'package:flutter_app/src/r.dart';
import 'package:flutter_app/src/util/file_utils.dart';
import 'package:flutter_app/ui/other/error_dialog.dart';

import 'file_store.dart';

class FileDownload {
static Future<void> download(String url, dirName, [String name = "", String referer]) async {
String path = await FileStore.getDownloadDir(dirName); //取得下載路徑
String realFileName;
String fileExtension;
static Future<void> download(String url, dirName, [String name = "", String? referer]) async {
final path = await FileStore.getDownloadDir(dirName);
String? realFileName = "";
String? fileExtension = "";
referer = referer ?? url;
Log.d("file download \n url: $url \n referer: $referer");
//顯示下載通知窗
ReceivedNotification value =
ReceivedNotification(title: name, body: R.current.prepareDownload, payload: null); //通知窗訊息
CancelToken cancelToken; //取消下載用
ProgressCallback onReceiveProgress; //下載進度回調
final value = ReceivedNotification(title: name, body: R.current.prepareDownload, payload: null); //通知窗訊息
final cancelToken = CancelToken();
await Notifications.instance.showIndeterminateProgressNotification(value);
//顯示下載進度通知窗
value.title = name;

int nowSize = 0;
onReceiveProgress = (int count, int total) async {
onReceiveProgress(int count, int total) {
value.body = FileUtils.formatBytes(count, 2);
if ((nowSize + 1024 * 128) > count && nowSize != 0) {
//128KB顯示一次
return;
}
nowSize = count;
if (count < total) {
Notifications.instance.showProgressNotification(value, 100, (count * 100 / total).round()); //顯示下載進度
} else {
Notifications.instance.showIndeterminateProgressNotification(value); //顯示下載進度
Notifications.instance.showIndeterminateProgressNotification(value);
}
};
//開始下載檔案
DioConnector.instance.download(url, (Headers responseHeaders) {
Map<String, List<String>> headers = responseHeaders.map;
if (headers.containsKey("content-disposition")) {
//代表有名字
List<String> name = headers["content-disposition"];
RegExp exp = RegExp("['|\"](?<name>.+)['|\"]"); //尋找 'name' , "name" 的name
RegExpMatch matches = exp.firstMatch(name[0]);
realFileName = matches.group(1);
} else if (headers.containsKey("content-type")) {
List<String> name = headers["content-type"];
if (name[0].toLowerCase().contains("pdf")) {
//是application/pdf
realFileName = '.pdf';
}

// This flag is used to prevent the dialog from being displayed multiple times.
bool hasError = false;
await DioConnector.instance.download(
url,
(responseHeaders) {
final Map<String, List<String>> headers = responseHeaders.map;
if (headers.containsKey("content-disposition")) {
final name = headers["content-disposition"];
final exp = RegExp("['|\"](?<name>.+)['|\"]");
final matches = name != null ? exp.firstMatch(name[0]) : null;
realFileName = matches?.group(1);
} else if (headers.containsKey("content-type")) {
final name = headers["content-type"];
if (name?[0].toLowerCase().contains("pdf") == true) {
realFileName = '.pdf';
}
}
}
if (!name.contains(".")) {
//代表名字不包含副檔名
if (realFileName != null) {
//代表可以從網路取得副檔名
fileExtension = realFileName.split(".").reversed.toList()[0];
realFileName = "$name.$fileExtension";
} else {
//嘗試使用網址後面找出附檔名
String maybeName = url.split("/").toList().last;
if (maybeName.contains(".")) {
fileExtension = maybeName.split(".").toList().last;
if (!name.contains(".")) {
if (realFileName != null) {
fileExtension = realFileName?.split(".").reversed.toList()[0];
realFileName = "$name.$fileExtension";
} else {
final maybeName = url.split("/").toList().last;
if (maybeName.contains(".")) {
fileExtension = maybeName.split(".").toList().last;
realFileName = "$name.$fileExtension";
}
}
} else {
final List<String> s = name.split(".");
s.removeLast();
if (realFileName != null && realFileName?.contains(".") == true) {
realFileName = '${s.join()}.${realFileName?.split(".").last ?? ""}';
}
//realFileName = name + "." + fileExtension;
}
} else {
//代表包含.
List<String> s = name.split(".");
s.removeLast();
if (realFileName != null && realFileName.contains(".")) {
realFileName = '${s.join()}.${realFileName.split(".").last}';
}
}
realFileName = realFileName ?? name; //如果還是沒有找到副檔名直接使用原始名稱
//print(path + "/" + realFileName);
return "$path/$realFileName";
}, progressCallback: onReceiveProgress, cancelToken: cancelToken, header: {"referer": referer}).whenComplete(
() async {
//顯示下載萬完成通知窗
await Notifications.instance.cancelNotification(value.id);
value.body = R.current.downloadComplete;
value.id = Notifications.instance.notificationId; //取得新的id
String filePath = '$path/$realFileName';
int id = value.id;
value.payload = json.encode({
"type": "download_complete",
"path": filePath,
"id": id,
});
await Notifications.instance.showNotification(value); //顯示下載完成
realFileName = realFileName ?? name;
return "$path/$realFileName";
},
progressCallback: onReceiveProgress,
cancelToken: cancelToken,
header: {"referer": referer},
).catchError(
(onError) async {
//顯示下載萬完成通知窗
hasError = true;
Log.d(onError.toString());
await Future.delayed(const Duration(milliseconds: 100));
Notifications.instance.cancelNotification(value.id);
value.body = "下載失敗";
value.id = Notifications.instance.notificationId; //取得新的id
int id = value.id;

value.body = R.current.downloadError;
value.id = Notifications.instance.notificationId;
value.payload = json.encode({
"type": "download_fail",
"id": id,
"id": value.id,
});
await Notifications.instance.showNotification(value); //顯示下載完成

await Notifications.instance.showNotification(value);
ErrorDialog(ErrorDialogParameter(
desc: realFileName,
title: R.current.downloadError,
offCancelBtn: true,
dialogType: DialogType.warning,
)).show();
},
);

if (!hasError) {
await Notifications.instance.cancelNotification(value.id);
value.body = R.current.downloadComplete;
value.id = Notifications.instance.notificationId;
value.payload = json.encode({
"type": "download_complete",
"path": '$path/$realFileName',
"id": value.id,
});
await Notifications.instance.showNotification(value);
ErrorDialog(ErrorDialogParameter(
desc: realFileName,
title: R.current.downloadComplete,
offCancelBtn: true,
dialogType: DialogType.success,
)).show();
}
}
}
11 changes: 5 additions & 6 deletions lib/src/file/file_store.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// TODO: remove sdk version selector after migrating to null-safety.
// @dart=2.10
import 'dart:convert';
import 'dart:io';

Expand All @@ -19,11 +17,12 @@ class FileStore {
return '';
}

final directory = await _getFilePath() ?? Platform.isAndroid
final filePath = await _getFilePath();
final directory = filePath != null || Platform.isAndroid
? await getExternalStorageDirectory()
: await getApplicationSupportDirectory();

final targetDir = Directory('${directory.path}/TAT');
final targetDir = Directory('${directory?.path ?? ''}/TAT');
final hasExisted = await targetDir.exists();
if (!hasExisted) {
targetDir.create();
Expand All @@ -44,7 +43,7 @@ class FileStore {
return savedDir.path;
}

static Future<bool> setFilePath(String directory) async {
static Future<bool> setFilePath(String? directory) async {
if (directory != null) {
final pref = await SharedPreferences.getInstance();
pref.setString(storeKey, base64Encode(directory.codeUnits));
Expand All @@ -54,7 +53,7 @@ class FileStore {
return false;
}

static Future<Directory> _getFilePath() async {
static Future<Directory?> _getFilePath() async {
final pref = await SharedPreferences.getInstance();
final path = pref.getString(storeKey);
if (path != null && path.isNotEmpty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ class AutoRollCallScheduleRepositoryImpl implements AutoRollCallScheduleReposito
required FirebaseMessaging firebaseMessaging,
}) : assert(firebaseAuth.currentUser != null),
_firebaseAuth = firebaseAuth,
_firebaseMessaging = firebaseMessaging {
_createUserDocumentIfNotExists();
}
_firebaseMessaging = firebaseMessaging;

final FirebaseAuth _firebaseAuth;
final FirebaseMessaging _firebaseMessaging;
Expand All @@ -39,7 +37,8 @@ class AutoRollCallScheduleRepositoryImpl implements AutoRollCallScheduleReposito
return true;
}

Future<void> _createUserDocumentIfNotExists() async {
// TODO: call this method when user sign in, if the auto roll call feature is enabled.
Future<void> createUserDocumentIfNotExists() async {
final isSignedIn = _checkUserSignedIn();
if (!isSignedIn) return;

Expand Down
10 changes: 1 addition & 9 deletions lib/src/util/permissions_util.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// TODO: remove sdk version selector after migrating to null-safety.
// @dart=2.10
import 'dart:io';

import 'package:permission_handler/permission_handler.dart';
Expand All @@ -15,12 +13,6 @@ class PermissionsUtil {
if (Platform.isIOS) return true;
assert(Platform.isAndroid, 'The platform most be either aos or ios.');

final storagePermissionStatus = await Permission.storage.status;
if (storagePermissionStatus != PermissionStatus.granted) {
final requestedPermissions = await [Permission.storage].request();
return requestedPermissions[Permission.storage] == PermissionStatus.granted;
}

return true;
return await Permission.storage.request().isGranted;
}
}
17 changes: 8 additions & 9 deletions lib/ui/other/my_toast.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// TODO: remove sdk version selector after migrating to null-safety.
// @dart=2.10
import 'package:flutter/material.dart';
import 'package:flutter_app/src/config/app_colors.dart';
import 'package:fluttertoast/fluttertoast.dart';

class MyToast {
static void show(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: AppColors.mainColor,
textColor: Colors.white,
fontSize: 16.0);
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: AppColors.mainColor,
textColor: Colors.white,
fontSize: 16.0,
);
}
}
Loading

0 comments on commit d504e08

Please sign in to comment.