SongBrowserUI.cs 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine.UI;
  6. using HMUI;
  7. using VRUI;
  8. using SongBrowserPlugin.DataAccess;
  9. using System.IO;
  10. using SongLoaderPlugin;
  11. using System.Security.Cryptography;
  12. using System.Text;
  13. namespace SongBrowserPlugin.UI
  14. {
  15. /// <summary>
  16. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  17. /// </summary>
  18. public class SongBrowserUI : MonoBehaviour
  19. {
  20. // Logging
  21. public const String Name = "SongBrowserUI";
  22. private const float SEGMENT_PERCENT = 0.1f;
  23. private Logger _log = new Logger(Name);
  24. // Beat Saber UI Elements
  25. private StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
  26. private StandardLevelListViewController _levelListViewController;
  27. private StandardLevelDetailViewController _levelDetailViewController;
  28. private StandardLevelDifficultyViewController _levelDifficultyViewController;
  29. private StandardLevelSelectionNavigationController _levelSelectionNavigationController;
  30. private StandardLevelListTableView _levelListTableView;
  31. private RectTransform _tableViewRectTransform;
  32. private Button _tableViewPageUpButton;
  33. private Button _tableViewPageDownButton;
  34. private Button _playButton;
  35. // New UI Elements
  36. private List<SongSortButton> _sortButtonGroup;
  37. private List<SongFilterButton> _filterButtonGroup;
  38. private Button _addFavoriteButton;
  39. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  40. private SimpleDialogPromptViewController _deleteDialog;
  41. private Button _deleteButton;
  42. private Button _pageUpTenPercent;
  43. private Button _pageDownTenPercent;
  44. private Button _enterFolderButton;
  45. private Button _upFolderButton;
  46. private SearchKeyboardViewController _searchViewController;
  47. // Cached items
  48. private Sprite _addFavoriteSprite;
  49. private Sprite _removeFavoriteSprite;
  50. private Sprite _currentAddFavoriteButtonSprite;
  51. // Debug
  52. private int _sortButtonLastPushedIndex = 0;
  53. private int _lastRow = 0;
  54. // Model
  55. private SongBrowserModel _model;
  56. /// <summary>
  57. /// Constructor
  58. /// </summary>
  59. public SongBrowserUI() : base()
  60. {
  61. if (_model == null)
  62. {
  63. _model = new SongBrowserModel();
  64. }
  65. _model.Init();
  66. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  67. }
  68. /// <summary>
  69. /// Builds the UI for this plugin.
  70. /// </summary>
  71. public void CreateUI()
  72. {
  73. _log.Trace("CreateUI()");
  74. try
  75. {
  76. if (_levelSelectionFlowCoordinator == null)
  77. {
  78. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  79. }
  80. if (_levelListViewController == null)
  81. {
  82. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  83. }
  84. if (_levelDetailViewController == null)
  85. {
  86. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  87. }
  88. if (_levelSelectionNavigationController == null)
  89. {
  90. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  91. }
  92. if (_levelDifficultyViewController == null)
  93. {
  94. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  95. }
  96. if (_levelListTableView == null)
  97. {
  98. _levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  99. }
  100. _playButton = _levelDetailViewController.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  101. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  102. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  103. this._deleteDialog.gameObject.SetActive(false);
  104. this._addFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.AddToFavoritesIcon);
  105. this._removeFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.RemoveFromFavoritesIcon);
  106. this.CreateUIElements();
  107. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  108. }
  109. catch (Exception e)
  110. {
  111. _log.Exception("Exception during CreateUI: ", e);
  112. }
  113. }
  114. /// <summary>
  115. /// Builds the SongBrowser UI
  116. /// </summary>
  117. private void CreateUIElements()
  118. {
  119. _log.Trace("CreateUIElements");
  120. try
  121. {
  122. // Gather some transforms and templates to use.
  123. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  124. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  125. Button sortButtonTemplate = _playButton;
  126. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
  127. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  128. // Resize some of the UI
  129. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  130. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  131. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  132. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  133. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  134. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  135. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  136. // Create Sorting Songs By-Buttons
  137. _log.Debug("Creating sort by buttons...");
  138. float fontSize = 2.35f;
  139. float buttonWidth = 13.50f;
  140. float buttonHeight = 5.5f;
  141. float buttonX = -61;
  142. float buttonY = 74.0f;
  143. string[] sortButtonNames = new string[]
  144. {
  145. "Song", "Author", "Original", "Newest", "Plays", "Difficult", "Random"
  146. };
  147. SongSortMode[] sortModes = new SongSortMode[]
  148. {
  149. SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.Difficulty, SongSortMode.Random
  150. };
  151. _sortButtonGroup = new List<SongSortButton>();
  152. for (int i = 0; i < sortButtonNames.Length; i++)
  153. {
  154. _sortButtonGroup.Add(UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  155. sortButtonNames[i],
  156. fontSize,
  157. buttonX + (buttonWidth * i),
  158. buttonY,
  159. buttonWidth,
  160. buttonHeight,
  161. sortModes[i],
  162. onSortButtonClickEvent));
  163. }
  164. // Create filter buttons
  165. float filterButtonX = buttonX + (buttonWidth * (sortButtonNames.Length - 1)) + (buttonWidth / 2.0f) + 2.5f;
  166. Vector2 iconButtonSize = new Vector2(5.5f, buttonHeight);
  167. Sprite playlistSprite = Base64Sprites.Base64ToSprite(Base64Sprites.PlaylistIcon);
  168. Sprite searchSprite = Base64Sprites.Base64ToSprite(Base64Sprites.SearchIcon);
  169. List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
  170. {
  171. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, onFavoriteFilterButtonClickEvent, _addFavoriteSprite),
  172. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, onPlaylistButtonClickEvent, playlistSprite),
  173. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, onSearchButtonClickEvent, searchSprite),
  174. };
  175. _filterButtonGroup = new List<SongFilterButton>();
  176. for (int i = 0; i < filterButtonSetup.Count; i++)
  177. {
  178. Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
  179. Button b = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate,
  180. t.Item3,
  181. new Vector2(filterButtonX + (iconButtonSize.x * i), buttonY),
  182. new Vector2(iconButtonSize.x, iconButtonSize.y),
  183. new Vector2(0, 0),
  184. new Vector2(3.5f, 3.5f),
  185. new Vector2(1.0f, 1.0f),
  186. 0);
  187. SongFilterButton filterButton = new SongFilterButton();
  188. filterButton.Button = b;
  189. filterButton.FilterMode = t.Item1;
  190. b.onClick.AddListener(t.Item2);
  191. _filterButtonGroup.Add(filterButton);
  192. }
  193. // Create Add to Favorites Button
  194. Vector2 addFavoritePos = new Vector2(40f, (sortButtonTemplate.transform as RectTransform).anchoredPosition.y);
  195. _addFavoriteButton = UIBuilder.CreateIconButton(otherButtonTransform, otherButtonTemplate, null,
  196. new Vector2(addFavoritePos.x, addFavoritePos.y),
  197. new Vector2(10.0f, 10.0f),
  198. new Vector2(2f, -1.5f),
  199. new Vector2(7.0f, 7.0f),
  200. new Vector2(1.0f, 1.0f),
  201. 0.0f);
  202. _addFavoriteButton.onClick.AddListener(delegate () {
  203. ToggleSongInFavorites();
  204. });
  205. if (_currentAddFavoriteButtonSprite == null)
  206. {
  207. IStandardLevel level = this._levelListViewController.selectedLevel;
  208. if (level != null)
  209. {
  210. RefreshAddFavoriteButton(level.levelID);
  211. }
  212. }
  213. // Create delete button
  214. _deleteButton = UIBuilder.CreateButton(otherButtonTransform, otherButtonTemplate, "Delete", fontSize, 46f, 0f, 15f, 5f);
  215. _deleteButton.onClick.AddListener(delegate () {
  216. HandleDeleteSelectedLevel();
  217. });
  218. // Create fast scroll buttons
  219. _pageUpTenPercent = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  220. new Vector2(15, 67.5f),
  221. new Vector2(6.0f, 5.5f),
  222. new Vector2(0f, 0f),
  223. new Vector2(1.5f, 1.5f),
  224. new Vector2(2.0f, 2.0f),
  225. 180);
  226. _pageUpTenPercent.onClick.AddListener(delegate () {
  227. this.JumpSongList(-1, SEGMENT_PERCENT);
  228. });
  229. _pageDownTenPercent = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  230. new Vector2(15, 0.5f),
  231. new Vector2(6.0f, 5.5f),
  232. new Vector2(0f, 0f),
  233. new Vector2(1.5f, 1.5f),
  234. new Vector2(2.0f, 2.0f),
  235. 0);
  236. _pageDownTenPercent.onClick.AddListener(delegate () {
  237. this.JumpSongList(1, SEGMENT_PERCENT);
  238. });
  239. // Create enter folder button
  240. if (_model.Settings.folderSupportEnabled)
  241. {
  242. _enterFolderButton = UIBuilder.CreateUIButton(otherButtonTransform, _playButton);
  243. _enterFolderButton.onClick.AddListener(delegate ()
  244. {
  245. _model.PushDirectory(_levelListViewController.selectedLevel);
  246. this.RefreshSongList();
  247. this.RefreshDirectoryButtons();
  248. });
  249. UIBuilder.SetButtonText(ref _enterFolderButton, "Enter");
  250. // Create up folder button
  251. _upFolderButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  252. new Vector2(filterButtonX + (iconButtonSize.x* filterButtonSetup.Count), buttonY),
  253. new Vector2(iconButtonSize.x, iconButtonSize.y),
  254. new Vector2(0f, 0f),
  255. new Vector2(0.85f, 0.85f),
  256. new Vector2(2.0f, 2.0f),
  257. 180);
  258. _upFolderButton.onClick.RemoveAllListeners();
  259. _upFolderButton.onClick.AddListener(delegate ()
  260. {
  261. _model.PopDirectory();
  262. this.RefreshSongList();
  263. this.RefreshDirectoryButtons();
  264. });
  265. }
  266. RefreshSortButtonUI();
  267. RefreshDirectoryButtons();
  268. }
  269. catch (Exception e)
  270. {
  271. _log.Exception("Exception CreateUIElements:", e);
  272. }
  273. }
  274. /// <summary>
  275. /// Sort button clicked.
  276. /// </summary>
  277. private void onSortButtonClickEvent(SongSortMode sortMode)
  278. {
  279. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  280. _model.LastSelectedLevelId = null;
  281. if (_model.Settings.sortMode == sortMode)
  282. {
  283. _model.ToggleInverting();
  284. }
  285. _model.Settings.sortMode = sortMode;
  286. _model.Settings.Save();
  287. UpdateSongList();
  288. RefreshSongList();
  289. // Handle instant queue logic, avoid picking a folder.
  290. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  291. {
  292. for (int i = 0; i < _model.SortedSongList.Count; i++)
  293. {
  294. if (!_model.SortedSongList[i].levelID.StartsWith("Folder_"))
  295. {
  296. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[i].levelID);
  297. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, _model.SortedSongList[i].difficultyBeatmaps.Length-1);
  298. _playButton.onClick.Invoke();
  299. break;
  300. }
  301. }
  302. }
  303. }
  304. /// <summary>
  305. /// Filter by favorites.
  306. /// </summary>
  307. private void onFavoriteFilterButtonClickEvent()
  308. {
  309. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
  310. if (_model.Settings.filterMode != SongFilterMode.Favorites)
  311. {
  312. _model.Settings.filterMode = SongFilterMode.Favorites;
  313. }
  314. else
  315. {
  316. _model.Settings.filterMode = SongFilterMode.None;
  317. }
  318. _model.Settings.Save();
  319. UpdateSongList();
  320. RefreshSongList();
  321. }
  322. /// <summary>
  323. /// Filter button clicked.
  324. /// </summary>
  325. /// <param name="sortMode"></param>
  326. private void onSearchButtonClickEvent()
  327. {
  328. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  329. if (_model.Settings.filterMode != SongFilterMode.Search)
  330. {
  331. _model.Settings.filterMode = SongFilterMode.Search;
  332. this.ShowSearchKeyboard();
  333. }
  334. else
  335. {
  336. _model.Settings.filterMode = SongFilterMode.None;
  337. UpdateSongList();
  338. RefreshSongList();
  339. }
  340. _model.Settings.Save();
  341. }
  342. /// <summary>
  343. /// Display the playlist selector.
  344. /// </summary>
  345. /// <param name="sortMode"></param>
  346. private void onPlaylistButtonClickEvent()
  347. {
  348. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  349. _model.LastSelectedLevelId = null;
  350. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  351. {
  352. PlaylistFlowCoordinator view = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  353. view.didSelectPlaylist += HandleDidSelectPlaylist;
  354. view.Present(_levelSelectionNavigationController);
  355. }
  356. else
  357. {
  358. _model.Settings.filterMode = SongFilterMode.None;
  359. _model.Settings.Save();
  360. UpdateSongList();
  361. RefreshSongList();
  362. }
  363. }
  364. /// <summary>
  365. /// Adjust UI based on level selected.
  366. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  367. /// </summary>
  368. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  369. {
  370. try
  371. {
  372. _log.Trace("OnDidSelectLevelEvent()");
  373. if (level == null)
  374. {
  375. _log.Debug("No level selected?");
  376. return;
  377. }
  378. if (_model.Settings == null)
  379. {
  380. _log.Debug("Settings not instantiated yet?");
  381. return;
  382. }
  383. _model.LastSelectedLevelId = level.levelID;
  384. RefreshAddFavoriteButton(level.levelID);
  385. RefreshQuickScrollButtons();
  386. if (level.levelID.StartsWith("Folder_"))
  387. {
  388. _log.Debug("Folder selected! Adjust PlayButton logic...");
  389. HandleDidSelectFolderRow(level);
  390. }
  391. else
  392. {
  393. HandleDidSelectLevelRow(level);
  394. }
  395. }
  396. catch (Exception e)
  397. {
  398. _log.Exception("Exception selecting song:", e);
  399. }
  400. }
  401. /// <summary>
  402. ///
  403. /// </summary>
  404. private void HandleDidSelectFolderRow(IStandardLevel level)
  405. {
  406. _enterFolderButton.gameObject.SetActive(true);
  407. _playButton.gameObject.SetActive(false);
  408. }
  409. /// <summary>
  410. ///
  411. /// </summary>
  412. /// <param name="level"></param>
  413. private void HandleDidSelectLevelRow(IStandardLevel level)
  414. {
  415. if (_enterFolderButton != null)
  416. {
  417. _enterFolderButton.gameObject.SetActive(false);
  418. }
  419. _playButton.gameObject.SetActive(true);
  420. }
  421. /// <summary>
  422. /// Pop up a delete dialog.
  423. /// </summary>
  424. private void HandleDeleteSelectedLevel()
  425. {
  426. IStandardLevel level = this._levelListViewController.selectedLevel;
  427. if (level == null)
  428. {
  429. _log.Info("No level selected, cannot delete nothing...");
  430. return;
  431. }
  432. if (level.levelID.StartsWith("Level"))
  433. {
  434. _log.Debug("Cannot delete non-custom levels.");
  435. return;
  436. }
  437. if (level.levelID.StartsWith("Folder"))
  438. {
  439. _log.Debug("Cannot delete folders.");
  440. return;
  441. }
  442. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  443. this._deleteDialog.Init("Delete level warning!", String.Format("<color=#00AAFF>Permanently delete level: {0}</color>\n Do you want to continue?", customLevel.songName), "YES", "NO");
  444. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  445. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  446. }
  447. /// <summary>
  448. /// Handle delete dialog resolution.
  449. /// </summary>
  450. /// <param name="viewController"></param>
  451. /// <param name="ok"></param>
  452. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  453. {
  454. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  455. if (!ok)
  456. {
  457. viewController.DismissModalViewController(null, false);
  458. }
  459. else
  460. {
  461. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  462. IStandardLevel level = this._levelListViewController.selectedLevel;
  463. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  464. string songPath = customLevel.customSongInfo.path;
  465. bool isZippedSong = false;
  466. viewController.DismissModalViewController(null, false);
  467. _log.Debug("Deleting: {0}", songPath);
  468. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  469. {
  470. isZippedSong = true;
  471. }
  472. if (isZippedSong)
  473. {
  474. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  475. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  476. Directory.Delete(songHashDir.FullName, true);
  477. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  478. {
  479. string hash = CreateMD5FromFile(file);
  480. if (hash != null)
  481. {
  482. if (hash == songHashDir.Name)
  483. {
  484. _log.Debug("Deleting zipped song: {0}", file);
  485. File.Delete(file);
  486. break;
  487. }
  488. }
  489. }
  490. }
  491. else
  492. {
  493. FileAttributes attr = File.GetAttributes(songPath);
  494. if (attr.HasFlag(FileAttributes.Directory))
  495. {
  496. _log.Debug("Deleting song: {0}", songPath);
  497. Directory.Delete(songPath, true);
  498. }
  499. }
  500. _model.LastSelectedLevelId = null;
  501. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  502. this.UpdateSongList();
  503. this.RefreshSongList();
  504. }
  505. }
  506. /// <summary>
  507. /// Create MD5 of a file.
  508. /// </summary>
  509. /// <param name="path"></param>
  510. /// <returns></returns>
  511. public static string CreateMD5FromFile(string path)
  512. {
  513. string hash = "";
  514. if (!File.Exists(path)) return null;
  515. using (MD5 md5 = MD5.Create())
  516. {
  517. using (var stream = File.OpenRead(path))
  518. {
  519. byte[] hashBytes = md5.ComputeHash(stream);
  520. StringBuilder sb = new StringBuilder();
  521. foreach (byte hashByte in hashBytes)
  522. {
  523. sb.Append(hashByte.ToString("X2"));
  524. }
  525. hash = sb.ToString();
  526. return hash;
  527. }
  528. }
  529. }
  530. /// <summary>
  531. /// Handle selection of a playlist. Show just the songs in the playlist.
  532. /// </summary>
  533. /// <param name="p"></param>
  534. private void HandleDidSelectPlaylist(Playlist p)
  535. {
  536. _log.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  537. _model.Settings.filterMode = SongFilterMode.Playlist;
  538. _model.CurrentPlaylist = p;
  539. _model.Settings.Save();
  540. this.UpdateSongList();
  541. this.RefreshSongList();
  542. }
  543. /// <summary>
  544. /// Display the search keyboard
  545. /// </summary>
  546. void ShowSearchKeyboard()
  547. {
  548. if (_searchViewController == null)
  549. {
  550. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  551. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  552. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  553. }
  554. _log.Debug("Presenting keyboard");
  555. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  556. }
  557. /// <summary>
  558. /// Handle back button event from search keyboard.
  559. /// </summary>
  560. private void SearchViewControllerbackButtonPressed()
  561. {
  562. }
  563. /// <summary>
  564. /// Handle search.
  565. /// </summary>
  566. /// <param name="searchFor"></param>
  567. private void SearchViewControllerSearchButtonPressed(string searchFor)
  568. {
  569. _log.Debug("Searching for \"{0}\"...", searchFor);
  570. _model.Settings.searchTerms.Insert(0, searchFor);
  571. _model.Settings.Save();
  572. _model.LastSelectedLevelId = null;
  573. this.UpdateSongList();
  574. this.RefreshSongList();
  575. }
  576. /// <summary>
  577. /// Make big jumps in the song list.
  578. /// </summary>
  579. /// <param name="numJumps"></param>
  580. private void JumpSongList(int numJumps, float segmentPercent)
  581. {
  582. int totalSize = _model.SortedSongList.Count;
  583. int segmentSize = (int)(totalSize * segmentPercent);
  584. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  585. HashSet<int> rows = tableView.GetPrivateField<HashSet<int>>("_selectedRows");
  586. int listSegment = (rows.First() / segmentSize);
  587. int newSegment = listSegment + numJumps;
  588. int newRow = 0;
  589. if (newSegment > 0)
  590. {
  591. newRow = Math.Min(newSegment * segmentSize, totalSize - 1);
  592. }
  593. _log.Debug("ListSegment: {0}, newRow: {1}", listSegment, newRow);
  594. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  595. }
  596. /// <summary>
  597. /// Add/Remove song from favorites depending on if it already exists.
  598. /// </summary>
  599. private void ToggleSongInFavorites()
  600. {
  601. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  602. if (_model.Settings.Favorites.Contains(songInfo.levelID))
  603. {
  604. _log.Info("Remove {0} from favorites", songInfo.songName);
  605. _model.Settings.Favorites.Remove(songInfo.levelID);
  606. }
  607. else
  608. {
  609. _log.Info("Add {0} to favorites", songInfo.songName);
  610. _model.Settings.Favorites.Add(songInfo.levelID);
  611. }
  612. RefreshAddFavoriteButton(songInfo.levelID);
  613. _model.Settings.SaveFavorites();
  614. }
  615. /// <summary>
  616. /// Update interactive state of the quick scroll buttons.
  617. /// </summary>
  618. private void RefreshQuickScrollButtons()
  619. {
  620. // Refresh the fast scroll buttons
  621. _pageUpTenPercent.interactable = _tableViewPageUpButton.interactable;
  622. _pageUpTenPercent.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  623. _pageDownTenPercent.interactable = _tableViewPageDownButton.interactable;
  624. _pageDownTenPercent.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  625. }
  626. /// <summary>
  627. /// Helper to quickly refresh add to favorites button
  628. /// </summary>
  629. /// <param name="levelId"></param>
  630. private void RefreshAddFavoriteButton(String levelId)
  631. {
  632. if (levelId == null)
  633. {
  634. _currentAddFavoriteButtonSprite = null;
  635. }
  636. else
  637. {
  638. if (_model.Settings.Favorites.Contains(levelId))
  639. {
  640. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  641. }
  642. else
  643. {
  644. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  645. }
  646. }
  647. UIBuilder.SetButtonIcon(ref _addFavoriteButton, _currentAddFavoriteButtonSprite);
  648. }
  649. /// <summary>
  650. /// Adjust the UI colors.
  651. /// </summary>
  652. public void RefreshSortButtonUI()
  653. {
  654. // So far all we need to refresh is the sort buttons.
  655. foreach (SongSortButton sortButton in _sortButtonGroup)
  656. {
  657. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  658. if (sortButton.SortMode == _model.Settings.sortMode)
  659. {
  660. if (_model.InvertingResults)
  661. {
  662. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  663. }
  664. else
  665. {
  666. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  667. }
  668. }
  669. }
  670. // refresh filter buttons
  671. foreach (SongFilterButton filterButton in _filterButtonGroup)
  672. {
  673. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.clear);
  674. if (filterButton.FilterMode == _model.Settings.filterMode)
  675. {
  676. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.green);
  677. }
  678. }
  679. }
  680. /// <summary>
  681. /// Refresh the UI state of any directory buttons.
  682. /// </summary>
  683. public void RefreshDirectoryButtons()
  684. {
  685. // bail if no button, likely folder support not enabled.
  686. if (_upFolderButton == null)
  687. {
  688. return;
  689. }
  690. if (_model.DirStackSize > 1)
  691. {
  692. _upFolderButton.interactable = true;
  693. }
  694. else
  695. {
  696. _upFolderButton.interactable = false;
  697. }
  698. }
  699. /// <summary>
  700. /// Try to refresh the song list. Broken for now.
  701. /// </summary>
  702. public void RefreshSongList()
  703. {
  704. _log.Info("Refreshing the song list view.");
  705. try
  706. {
  707. if (_model.SortedSongList == null)
  708. {
  709. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  710. return;
  711. }
  712. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  713. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  714. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  715. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  716. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  717. tableView.ReloadData();
  718. String selectedLevelID = null;
  719. if (_model.LastSelectedLevelId != null)
  720. {
  721. selectedLevelID = _model.LastSelectedLevelId;
  722. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  723. }
  724. else
  725. {
  726. if (levels.Length > 0)
  727. {
  728. selectedLevelID = levels.FirstOrDefault().levelID;
  729. }
  730. }
  731. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  732. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  733. {
  734. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  735. }
  736. RefreshSortButtonUI();
  737. RefreshQuickScrollButtons();
  738. }
  739. catch (Exception e)
  740. {
  741. _log.Exception("Exception refreshing song list:", e);
  742. }
  743. }
  744. /// <summary>
  745. /// Scroll TableView to proper row, fire events.
  746. /// </summary>
  747. /// <param name="table"></param>
  748. /// <param name="levelID"></param>
  749. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  750. {
  751. int row = table.RowNumberForLevelID(levelID);
  752. TableView tableView = table.GetComponentInChildren<TableView>();
  753. tableView.SelectRow(row, true);
  754. tableView.ScrollToRow(row, true);
  755. _lastRow = row;
  756. }
  757. /// <summary>
  758. /// Helper for updating the model (which updates the song list)c
  759. /// </summary>
  760. public void UpdateSongList()
  761. {
  762. _log.Trace("UpdateSongList()");
  763. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  764. _model.UpdateSongLists(gameplayMode);
  765. this.RefreshDirectoryButtons();
  766. }
  767. /// <summary>
  768. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  769. /// </summary>
  770. public void LateUpdate()
  771. {
  772. CheckDebugUserInput();
  773. }
  774. /// <summary>
  775. /// Map some key presses directly to UI interactions to make testing easier.
  776. /// </summary>
  777. private void CheckDebugUserInput()
  778. {
  779. try
  780. {
  781. if (this._levelListViewController != null && this._levelListViewController.isActiveAndEnabled)
  782. {
  783. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  784. // back
  785. if (Input.GetKeyDown(KeyCode.Escape))
  786. {
  787. this._levelSelectionNavigationController.DismissButtonWasPressed();
  788. }
  789. // cycle sort modes
  790. if (Input.GetKeyDown(KeyCode.T))
  791. {
  792. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  793. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  794. }
  795. // select current sort mode again (toggle inverting)
  796. if (Input.GetKeyDown(KeyCode.Y))
  797. {
  798. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  799. }
  800. // filter playlists
  801. if (Input.GetKeyDown(KeyCode.P))
  802. {
  803. onPlaylistButtonClickEvent();
  804. }
  805. // filter search
  806. if (Input.GetKeyDown(KeyCode.S))
  807. {
  808. onSearchButtonClickEvent();
  809. }
  810. // filter favorites
  811. if (Input.GetKeyDown(KeyCode.F))
  812. {
  813. onFavoriteFilterButtonClickEvent();
  814. }
  815. // delete
  816. if (Input.GetKeyDown(KeyCode.D))
  817. {
  818. _deleteButton.onClick.Invoke();
  819. }
  820. // c - select difficulty for top song
  821. if (Input.GetKeyDown(KeyCode.C))
  822. {
  823. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  824. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  825. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  826. }
  827. // v start a song or enter a folder
  828. if (Input.GetKeyDown(KeyCode.Return))
  829. {
  830. if (_playButton.isActiveAndEnabled)
  831. {
  832. _playButton.onClick.Invoke();
  833. }
  834. else if (_enterFolderButton.isActiveAndEnabled)
  835. {
  836. _enterFolderButton.onClick.Invoke();
  837. }
  838. }
  839. // backspace - up a folder
  840. if (Input.GetKeyDown(KeyCode.Backspace))
  841. {
  842. _upFolderButton.onClick.Invoke();
  843. }
  844. // change song index
  845. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  846. {
  847. _pageUpTenPercent.onClick.Invoke();
  848. }
  849. else if (Input.GetKeyDown(KeyCode.N))
  850. {
  851. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  852. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  853. }
  854. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  855. {
  856. _pageDownTenPercent.onClick.Invoke();
  857. }
  858. else if (Input.GetKeyDown(KeyCode.M))
  859. {
  860. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  861. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  862. }
  863. // add to favorites
  864. if (Input.GetKeyDown(KeyCode.KeypadPlus))
  865. {
  866. ToggleSongInFavorites();
  867. }
  868. }
  869. else if (_deleteDialog != null && _deleteDialog.isInViewControllerHierarchy)
  870. {
  871. // accept delete
  872. if (Input.GetKeyDown(KeyCode.Return))
  873. {
  874. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  875. }
  876. if (Input.GetKeyDown(KeyCode.Escape))
  877. {
  878. _deleteDialog.GetPrivateField<TextMeshProButton>("_cancelButton").button.onClick.Invoke();
  879. }
  880. }
  881. }
  882. catch (Exception e)
  883. {
  884. _log.Exception("Debug Input caused Exception: ", e);
  885. }
  886. }
  887. }
  888. }