Skip to content

Commit

Permalink
Nov 2023 update (#61)
Browse files Browse the repository at this point in the history
* remove deprectaed describeEnum

* use PopScope instead of WillPopScope

* update flutter version in cicd

* update assets

* export json to download folder

* update yaml version

* fix legacy android (< Q) json export
  • Loading branch information
EdwynZN authored Nov 27, 2023
1 parent a945bd0 commit afad104
Show file tree
Hide file tree
Showing 23 changed files with 263 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/qa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
env:
java_version: '11.0.14+9'
ruby_version: '2.7.2'
flutter_version: '3.13.0'
flutter_version: '3.16.0'

jobs:
android-QA:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
env:
java_version: '11.0.14+9'
ruby_version: '2.7.2'
flutter_version: '3.13.0'
flutter_version: '3.16.0'

jobs:
android-internal:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,14 @@ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
} catch(IOException e){
result.error("IOException: ", e.getMessage(), null);
}
/*
Map<String, Object> imgArguments = call.arguments();
String name = (String)imgArguments.get("name");
byte[] imageData = (byte[])imgArguments.get("buffer");
break;
case "saveJson":
try{
Bitmap bitmap;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) bitmap = MediaStoreFlutter.updateMediaStore(this, imageData, name);
else bitmap = MediaStoreFlutter.updateLegacyMediaStore(this, imageData, name);
mNotificationUtils.showJsonNotification(call.arguments());
result.success(true);
} catch(IOException e){
result.error("IOException: ", e.getMessage(), null);
}
*/
break;
default:
result.notImplemented();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,119 @@ static public Uri updateLegacyMediaStore(Context context, Bitmap bitmap, String
}
}

@RequiresApi(api = Build.VERSION_CODES.Q)
static public Uri updateDownloadStore(Context context, byte[] data, String nameFile) throws IOException {
ContentResolver resolver = context.getContentResolver();
Uri docCollection = MediaStore.Files.getContentUri("external");
Uri uri = existsFile(resolver, docCollection, nameFile);

ContentValues content = new ContentValues();
content.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
if(uri == null) {
content.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/json");
content.put(MediaStore.Files.FileColumns.DISPLAY_NAME, nameFile + ".json");
content.put(MediaStore.Files.FileColumns.TITLE, nameFile);
content.put(MediaStore.Files.FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS);
content.put(MediaStore.Files.FileColumns.OWNER_PACKAGE_NAME, BuildConfig.APPLICATION_ID);
content.put(MediaStore.Files.FileColumns.DATE_TAKEN, System.currentTimeMillis());
uri = resolver.insert(docCollection, content);
} else {
content.put(MediaStore.Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
resolver.update(uri, content, null, null);
}

try (OutputStream stream = resolver.openOutputStream(uri, "w")) {
stream.write(data, 0, data.length);
content.clear();
content.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
resolver.update(uri, content, null, null);
//Log.v("uri", uri.toString());
//Log.v("Content", imageCollection.toString());
return uri;
}
catch (IOException e) {
// Don't leave an orphan entry in the MediaStore
if (uri != null) resolver.delete(uri, null, null);
throw e;
}
}

static public Uri updateLegacyDownloadStore(Context context, byte[] data, String nameFile) throws IOException {
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
if(!directory.exists()) directory.mkdirs();

Uri downloadCollection = MediaStore.Files.getContentUri("external");
ContentResolver resolver = context.getContentResolver();
Uri uri = existsFile(resolver, downloadCollection, nameFile);

ContentValues content = new ContentValues();
if(uri == null) {
File file = new File(directory, nameFile + ".json");
content.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/json");
content.put(MediaStore.Files.FileColumns.DISPLAY_NAME, nameFile + ".json");
content.put(MediaStore.Files.FileColumns.TITLE, nameFile);
content.put(MediaStore.Files.FileColumns.DATE_TAKEN, System.currentTimeMillis());
content.put(MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME, folder);
content.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
uri = resolver.insert(downloadCollection, content);
}

try (OutputStream stream = resolver.openOutputStream(uri, "w")) {
stream.write(data, 0, data.length);
content.clear();
content.put(MediaStore.Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
resolver.update(uri, content, null, null);
return uri;
} catch (IOException e) {
if (uri != null) resolver.delete(uri, null, null);
throw e;
}
}

@Nullable
static private Uri existsFile(ContentResolver resolver, Uri uri, String nameFile) {
String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ? and " + MediaStore.Files.FileColumns.TITLE + " = ?";
String[] args = new String[] {"application/json", nameFile};

Cursor cursor = resolver.query(
uri,
new String[] {MediaStore.Files.FileColumns.MIME_TYPE, MediaStore.Files.FileColumns.TITLE, MediaStore.Files.FileColumns._ID},
selection,
args,
null
);
int id = 0;
if(cursor != null){
if(cursor.moveToFirst()) id = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
cursor.close();
if(id != 0) return Uri.withAppendedPath(uri, String.valueOf(id));
}
return null;
}

@Nullable
static private Uri existsLegacyFile(ContentResolver resolver, Uri uri, String nameFile) {
String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ? and " + MediaStore.Files.FileColumns.TITLE + " = ?";
String[] args = new String[] {"application/json", nameFile};

Cursor cursor = resolver.query(
uri,
new String[] {MediaStore.Files.FileColumns.MIME_TYPE, MediaStore.Files.FileColumns.TITLE, MediaStore.Files.FileColumns._ID},
selection,
args,
null
);
int id = 0;
if(cursor != null) {
if(cursor.moveToFirst()) id = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
cursor.close();
if(id != 0) return Uri.withAppendedPath(uri, String.valueOf(id));
}
return null;
}

@Nullable
static public Uri exists(ContentResolver resolver, Uri uri, String nameFile){
static public Uri exists(ContentResolver resolver, Uri uri, String nameFile) {
String selection = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " = ? and " + MediaStore.Images.Media.TITLE + " = ?";
String[] args = new String[] {folder, nameFile};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.DocumentsContract;
import java.io.File;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
Expand Down Expand Up @@ -103,9 +104,49 @@ private Notification createNotification(String title, String path, String action
return notificationBuilder.build();
}

private Notification createDownloadNotification(String title, Uri uri, String name, String actionTitle, Integer id) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, ANDROID_CHANNEL_ID);
notificationBuilder//.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification)
.setTicker(title)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentTitle(title)
.setGroup(GROUP_ID)
.setContentText(name);

Intent intent = new Intent();
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(uri, "application/json");

final int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
: PendingIntent.FLAG_UPDATE_CURRENT;
PendingIntent pContentIntent = PendingIntent.getActivity(this, id, intent, flag);

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
sendIntent.setType("application/json");
Intent chooser = Intent.createChooser(sendIntent, actionTitle);

List<ResolveInfo> resInfoList = this.getPackageManager().queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
this.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

PendingIntent pShareIntent = PendingIntent.getActivity(this, id, chooser, flag);

return notificationBuilder
.addAction(android.R.drawable.ic_menu_share, actionTitle, pShareIntent)
.setContentIntent(pContentIntent)
.build();
}

private Notification imageNotification(String title, String contentText, Uri uri, String actionTitle, Integer id, Bitmap bitmap) {
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, ANDROID_CHANNEL_ID);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, ANDROID_CHANNEL_ID);
notificationBuilder//.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification)
.setTicker(title)
Expand Down Expand Up @@ -198,4 +239,23 @@ public void showImageNotification(Map<String, Object> arguments) throws IOExcept
}
}

public void showJsonNotification(Map<String, Object> arguments) throws IOException{
String name = (String)arguments.get("name");
byte[] jsonBuffer = (byte[])arguments.get("buffer");
String title = (String) arguments.get("title");
String actionTitle = (String) arguments.get("actionTitle");
Integer id = (Integer) arguments.get("id");
if(jsonBuffer == null) return;
Uri uri;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) uri = MediaStoreFlutter.updateDownloadStore(this, jsonBuffer, name);
else uri = MediaStoreFlutter.updateLegacyDownloadStore(this, jsonBuffer, name);
Notification notification = createDownloadNotification(title, uri, name, actionTitle, id);
getManager().notify(id, notification);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Notification summary = createSummary(title);
getManager().notify(SUMMARY_ID, summary);
}
}

}
Binary file modified assets/collection/icon_784.webp
Binary file not shown.
Binary file modified assets/collection/icon_785.webp
Binary file not shown.
Binary file modified assets/collection/icon_786.webp
Binary file not shown.
Binary file modified assets/collection/icon_787.webp
Binary file not shown.
Binary file modified assets/collection/icon_836.webp
Binary file not shown.
Binary file modified assets/collection/icon_837.webp
Binary file not shown.
Binary file modified assets/collection/icon_838.webp
Binary file not shown.
3 changes: 2 additions & 1 deletion lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@
"no_games_found": "No games found for this amiibo yet",
"no_date": "To be announced",
"showGrid": "Grid",
"hide_caution": "Caution, disabling a category will hide it from all aspects of the app"
"hide_caution": "Caution, disabling a category will hide it from all aspects of the app",
"export_complete": "Export completed!"
}
3 changes: 2 additions & 1 deletion lib/l10n/intl_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@
"no_games_found": "No existe una lista de juegos para este amiibo aún",
"no_date": "Próximamente",
"showGrid": "Cuadrícula",
"hide_caution": "Precaución, deshabilitar una categoría la escondera en toda la app"
"hide_caution": "Precaución, deshabilitar una categoría la escondera en toda la app",
"export_complete": "Archivo guardado"
}
3 changes: 2 additions & 1 deletion lib/l10n/intl_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@
"no_games_found": "Aucun jeu trouvé pour cette amiibo pour le moment",
"no_date": "Bientôt disponible",
"showGrid": "Grille",
"hide_caution": "Attention, la désactivation d'une catégorie la masquera dans l'ensemble de l'application"
"hide_caution": "Attention, la désactivation d'une catégorie la masquera dans l'ensemble de l'application",
"export_complete": "Exportation terminée"
}
4 changes: 2 additions & 2 deletions lib/model/search_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ mixin _OrderBy {
late final SortBy sortBy;

String get order {
final String order = describeEnum(orderBy);
final String order = orderBy.name;
StringBuffer orderBuffer = StringBuffer();
final String sort = describeEnum(sortBy);
final String sort = sortBy.name;
switch (orderBy) {
case OrderBy.NA:
case OrderBy.JP:
Expand Down
3 changes: 1 addition & 2 deletions lib/riverpod/query_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:amiibo_network/enum/hidden_types.dart';
import 'package:amiibo_network/riverpod/preferences_provider.dart';
import 'package:amiibo_network/riverpod/service_provider.dart';
import 'package:amiibo_network/utils/preferences_constants.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod/riverpod.dart';
import 'package:amiibo_network/enum/amiibo_category_enum.dart';
Expand Down Expand Up @@ -163,7 +162,7 @@ class QueryBuilderProvider extends StateNotifier<QueryBuilder> {
switch (_query.category) {
case AmiiboCategory.Owned:
case AmiiboCategory.Wishlist:
where = Cond.iss(_query.search ?? describeEnum(_query.category), '1');
where = Cond.iss(_query.search ?? _query.category.name, '1');
break;
case AmiiboCategory.Figures:
where = InCond.inn('type', figureType);
Expand Down
48 changes: 26 additions & 22 deletions lib/screen/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import 'package:amiibo_network/widget/route_transitions.dart';
import 'package:amiibo_network/widget/selected_chip.dart';
import 'package:amiibo_network/widget/selected_widget.dart';
import 'package:amiibo_network/widget/sort_bottomsheet.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:amiibo_network/data/database.dart';
import 'package:flutter/rendering.dart';
Expand Down Expand Up @@ -67,6 +66,17 @@ final AutoDisposeProvider<TitleSearch> _titleProvider =
);
});

final AutoDisposeProvider<bool> _canPopProvider = Provider.autoDispose<bool>(
(ref) {
final selected = ref.watch(selectProvider);
ref.watch(queryProvider);
final isSearch = ref.watch(
queryProvider.notifier.select((provider) => provider.isSearch),
);
return !(selected.multipleSelected || isSearch);
},
);

class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({Key? key}) : super(key: key);

Expand Down Expand Up @@ -174,35 +184,29 @@ class HomeScreenState extends ConsumerState<HomeScreen>
}
}

Future<bool> _exitApp() async {
final route = ModalRoute.of(context);

/// If the route is current and it has local history then let it handle it by itself
if (route != null && route.isCurrent && route.willHandlePopInternally) {
return SynchronousFuture(true);
}

final selected = ref.read(selectProvider);
final query = ref.read(queryProvider.notifier);
if (selected.multipleSelected) {
selected.clearSelected();
return false;
} else if (query.isSearch) {
query.restart();
return false;
} else {
Future<void> _exitApp(bool canPop) async {
if (canPop) {
await ConnectionFactory().close();
return await Future.value(true);
} else {
final selected = ref.read(selectProvider);
final query = ref.read(queryProvider.notifier);
if (selected.multipleSelected) {
selected.clearSelected();
} else if (query.isSearch) {
query.restart();
}
}
}

@override
Widget build(BuildContext context) {
final isAmiiboList = index == 0;
final canPop = ref.watch(_canPopProvider);
return DashMenu(
leftDrawer: CollectionDrawer(restart: _restartAnimation),
body: WillPopScope(
onWillPop: _exitApp,
body: PopScope(
canPop: canPop,
onPopInvoked: _exitApp,
child: Scaffold(
resizeToAvoidBottomInset: false,
//drawer: CollectionDrawer(restart: _restartAnimation),
Expand Down Expand Up @@ -690,7 +694,7 @@ class _FAB extends ConsumerWidget {
constraints: BoxConstraints.loose(const Size.square(24.0)),
child: LoadingAnimationWidget.inkDrop(
color: theme.floatingActionButtonTheme.foregroundColor ??
theme.colorScheme.onSecondaryContainer,
theme.colorScheme.onSecondaryContainer,
size: 24,
),
)
Expand Down
Loading

0 comments on commit afad104

Please sign in to comment.