Skip to content

Commit

Permalink
feat: Create Cover Settings
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobaraujo7 committed Jan 25, 2024
1 parent 2a44c7e commit f84e725
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 54 deletions.
3 changes: 3 additions & 0 deletions assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "Channels",
"change_title": "Change title",
"change_cover": "Change Cover",
"cover": "Cover",
"import_covers": "Import covers",
"import_covers_description": "Choose a folder that contains\ngame covers.\nThe cover name must be the same as the game.",
"resync": "Resync",
"replace_player": "Replace player",
"remove": "Remove",
Expand Down
3 changes: 3 additions & 0 deletions assets/i18n/es_ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "Canales",
"change_title": "Cambiar título",
"change_cover": "Cambiar Portada",
"cover": "Portada",
"import_covers": "Importar Portadas",
"import_covers_description": "Elige una carpeta que contenga\nportadas de juegos.\nEl nombre de la portada debe ser el mismo que el del juego.",
"resync": "Re-sincronizar",
"replace_player": "Reemplazar jugador",
"remove": "Eliminar",
Expand Down
3 changes: 3 additions & 0 deletions assets/i18n/ja_JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "チャンネル",
"change_title": "タイトルを変更する",
"change_cover": "カバーを変更する",
"cover": "カバー",
"import_covers": "カバーをインポートする",
"import_covers_description": "ゲームのカバーをインポートするには、ゲームのフォルダに「cover.png」または「cover.jpg」ファイルを追加します。",
"resync": "再同期",
"replace_player": "プレイヤーを交換する",
"remove": "削除する",
Expand Down
3 changes: 3 additions & 0 deletions assets/i18n/pt_BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "Canais",
"change_title": "Mudar título",
"change_cover": "Mudar Capa",
"cover": "Capa",
"import_covers": "Importar Capas",
"import_covers_description": "Escolha um pasta que contenha\nas capas dos jogos.\nO nome da capa deve ser o mesmo do jogo.",
"resync": "Ressincronizar",
"replace_player": "Substituir player",
"remove": "Remover",
Expand Down
3 changes: 3 additions & 0 deletions assets/i18n/ru_RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "Каналы",
"change_title": "Изменить название",
"change_cover": "Изменить обложку",
"cover": "Обложка",
"import_covers": "Импортировать обложки",
"import_covers_description": "Выберите папку, содержащую\nобложки игр.\nНазвание обложки должно совпадать с названием игры.",
"resync": "Пересинхронизация",
"replace_player": "Заменить игрока",
"remove": "Удалить",
Expand Down
3 changes: 3 additions & 0 deletions assets/i18n/zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "频道",
"change_title": "更改标题",
"change_cover": "更改封面",
"cover": "封面",
"import_covers": "导入封面",
"import_covers_description": "您可以导入封面,以便在游戏列表中显示。如果您不导入封面,将使用默认封面。",
"resync": "重新同步",
"replace_player": "替换玩家",
"remove": "移除",
Expand Down
3 changes: 3 additions & 0 deletions assets/i18n/zh_TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"channels": "頻道",
"change_title": "更改標題",
"change_cover": "更改封面",
"cover": "封面",
"import_covers": "導入封面",
"import_covers_description": "您可以導入一個包含遊戲封面的資料夾,Yuno 將會為您的遊戲自動分配封面。",
"resync": "重新同步",
"replace_player": "替換玩家",
"remove": "移除",
Expand Down
4 changes: 4 additions & 0 deletions lib/app/(public)/config/config_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class _ConfigPageState extends State<ConfigPage> {
icon: const Icon(Icons.settings),
label: Text('preferences'.i18n()),
),
NavigationRailDestination(
icon: const Icon(Icons.image),
label: Text('cover'.i18n()),
),
NavigationRailDestination(
icon: const Icon(Icons.chat_outlined),
label: Text('feedback'.i18n()),
Expand Down
5 changes: 1 addition & 4 deletions lib/app/(public)/config/edit_platform_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:yuno/app/interactor/atoms/platform_atom.dart';
import 'package:yuno/app/interactor/models/embeds/game.dart';

import '../../core/widgets/animated_title_app_bart.dart';
import '../../interactor/actions/config_action.dart';
import '../../interactor/actions/platform_action.dart';
import '../../interactor/models/embeds/game_category.dart';
import '../../interactor/models/embeds/player.dart';
Expand Down Expand Up @@ -247,8 +248,4 @@ class _EditPlatformPageState extends State<EditPlatformPage> {
);
}

String beautifyPath(String dir) {
final path = convertContentUriToFilePath(dir);
return path.replaceAll('/storage/emulated/0', '');
}
}
68 changes: 67 additions & 1 deletion lib/app/(public)/config/widgets/cover_settings_widget.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,76 @@
import 'dart:ffi';

import 'package:asp/asp.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:localization/localization.dart';

import '../../../interactor/actions/config_action.dart';
import '../../../interactor/atoms/config_atom.dart';

class CoverSettingsWidget extends StatelessWidget {
const CoverSettingsWidget({super.key});

@override
Widget build(BuildContext context) {
return const Placeholder();
final theme = Theme.of(context);
return RxBuilder(
builder: (context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'cover'.i18n(),
style: theme.textTheme.titleMedium,
),
const Gap(12),
SwitchListTile(
value: gameConfigState.value.enableIGDB,
onChanged: (v) {
saveConfig(gameConfigState.value.copyWith(enableIGDB: v));
},
title: const Text('IGDB'),
),
const Gap(18),
Text(
'import_covers'.i18n(),
style: theme.textTheme.titleMedium,
),
const Gap(12),
Padding(
padding: const EdgeInsets.only(right: 20),
child: TextFormField(
key: Key(beautifyPath(gameConfigState.value.coverFolder ?? '')),
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'folder'.i18n(),
suffixIcon: const Icon(Icons.folder),
),
initialValue:
beautifyPath(gameConfigState.value.coverFolder ?? ''),
readOnly: true,
onTap: () async {
final selectedDirectory = await getDirectoryPath();

if (selectedDirectory != null) {
saveConfig(
gameConfigState.value.copyWith(
coverFolder: selectedDirectory,
),
);
}
},
),
),
const Gap(12),
Text(
'import_covers_description'.i18n(),
style: theme.textTheme.titleSmall,
),
],
);
},
);
}
}
131 changes: 96 additions & 35 deletions lib/app/data/repositories/uno_sync_repository.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:media_store_plus/media_store_plus.dart';
import 'package:path/path.dart';
import 'package:uno/uno.dart';
import 'package:yuno/app/interactor/models/embeds/game.dart';
Expand All @@ -14,49 +17,107 @@ class UnoSyncRepository implements SyncRepository {
UnoSyncRepository({required this.uno});

@override
Future<Game> syncIGDB(Game game) async {
final response = await uno.post(
'https://api.igdb.com/v4/games',
headers: {
'content-type': 'text/plain',
'client-id': igdbClientId,
'authorization': 'Bearer $igdbToken',
},
data:
'fields artworks,collection,cover.*, first_release_date,genres.*,name,summary; search "${game.name}"; limit 2;',
);

// prevent multiples calls
await Future.delayed(const Duration(seconds: 1));

if (response.data.isEmpty) {
Future<Game> syncLocalFolder(Game game, String coverFolder) async {
try {
final media = MediaStore();
final documents = await media.getDocumentTree(uriString: coverFolder);

if (documents == null) {
return game;
}

final files = documents //
.children
.where(
(doc) {
final name = doc.name?.toLowerCase();
if (name == null) {
return false;
}
return name.endsWith('.png') ||
name.endsWith('.jgp') ||
name.endsWith('.jpeg');
},
).toList();

if (files.isEmpty) {
return game;
}

final file = files.firstWhereOrNull((doc) {
final name = doc.name!.toLowerCase();
return name.startsWith(game.name.toLowerCase());
});

if (file == null) {
return game;
}

final dirPath = await pathProvider.getApplicationDocumentsDirectory();
final imageName = file.name!;
final pathSeparator = Platform.pathSeparator;
final imageFile = File('${dirPath.path}${pathSeparator}local_$imageName');
if (imageFile.existsSync()) {
await imageFile.delete();
}
await media.readFileUsingUri(
uriString: file.uriString, tempFilePath: imageFile.path);
return game.copyWith(image: imageFile.path);
} catch (e) {
debugPrint(e.toString());
return game;
}
}

final json = response.data[0];
@override
Future<Game> syncIGDB(Game game) async {
try {
final response = await uno.post(
'https://api.igdb.com/v4/games',
headers: {
'content-type': 'text/plain',
'client-id': igdbClientId,
'authorization': 'Bearer $igdbToken',
},
data:
'fields artworks,collection,cover.*, first_release_date,genres.*,name,summary; search "${game.name}"; limit 2;',
);

var image = "https:${json['cover']['url']}";
image = image.replaceAll('t_thumb', 't_cover_big');
// prevent multiples calls
await Future.delayed(const Duration(seconds: 1));

final dirPath = await pathProvider.getApplicationDocumentsDirectory();
final imageName = basename(image);
final pathSeparator = Platform.pathSeparator;
final imageFile = File('${dirPath.path}$pathSeparator$imageName');
if (response.data.isEmpty) {
return game;
}

if (!imageFile.existsSync()) {
final imageData =
await uno.get(image, responseType: ResponseType.arraybuffer);
await imageFile.writeAsBytes(imageData.data);
}
final json = response.data[0];

var image = "https:${json['cover']['url']}";
image = image.replaceAll('t_thumb', 't_cover_big');

final metaGame = game.copyWith(
isSynced: true,
description: json['summary'],
image: imageFile.path,
genre: json['genres'][0]['name'],
);
final dirPath = await pathProvider.getApplicationDocumentsDirectory();
final imageName = basename(image);
final pathSeparator = Platform.pathSeparator;
final imageFile = File('${dirPath.path}${pathSeparator}igdb_$imageName');

return metaGame;
if (!imageFile.existsSync()) {
final imageData =
await uno.get(image, responseType: ResponseType.arraybuffer);
await imageFile.writeAsBytes(imageData.data);
}

final metaGame = game.copyWith(
isSynced: true,
description: json['summary'],
image: imageFile.path,
genre: json['genres'][0]['name'],
);

return metaGame;
} catch (e) {
debugPrint(e.toString());
return game;
}
}

@override
Expand Down
6 changes: 6 additions & 0 deletions lib/app/interactor/actions/config_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:yuno/injector.dart';

import '../atoms/config_atom.dart';
import '../repositories/config_repository.dart';
import 'platform_action.dart';

Future<void> saveConfig(GameConfig config) async {
final repository = injector.get<ConfigRepository>();
Expand All @@ -25,6 +26,11 @@ Future<void> openUrl(Uri uri) async {
await repository.openUrl(uri);
}

String beautifyPath(String dir) {
final path = convertContentUriToFilePath(dir);
return path.replaceAll('/storage/emulated/0', '');
}

final _battery = Battery();
StreamSubscription<BatteryState>? _batterySubscription;

Expand Down
27 changes: 20 additions & 7 deletions lib/app/interactor/actions/platform_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import 'dart:io';

import 'package:collection/collection.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:media_store_plus/media_store_plus.dart';
import 'package:yuno/app/interactor/atoms/config_atom.dart';
import 'package:yuno/app/interactor/models/platform_model.dart';
import 'package:yuno/app/interactor/repositories/platform_repository.dart';
import 'package:yuno/app/interactor/repositories/sync_repository.dart';
Expand Down Expand Up @@ -86,14 +89,24 @@ Future<void> syncPlatform(PlatformModel platform) async {
final color = await getDominatingColor(platform.games[i].image);
platform.games[i] = platform.games[i].copyWith(imageColor: color);
} else {
try {
var metaGame = await repository.syncIGDB(platform.games[i]);
final color = await getDominatingColor(metaGame.image);
metaGame = metaGame.copyWith(imageColor: color);
platform.games[i] = metaGame;
} catch (e) {
continue;
Game metaGame = platform.games[i];

if (gameConfigState.value.coverFolder != null) {
metaGame = await repository.syncLocalFolder(
metaGame,
gameConfigState.value.coverFolder!,
);
}

if (gameConfigState.value.enableIGDB && metaGame.image.isEmpty) {
metaGame = await repository.syncIGDB(
platform.games[i],
);
}

final color = await getDominatingColor(metaGame.image);
metaGame = metaGame.copyWith(imageColor: color);
platform.games[i] = metaGame;
}
}

Expand Down
Loading

0 comments on commit f84e725

Please sign in to comment.