SongBrowserUI.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  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.5f;
  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, 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. }
  333. else
  334. {
  335. _model.Settings.filterMode = SongFilterMode.None;
  336. }
  337. _model.Settings.Save();
  338. this.ShowSearchKeyboard();
  339. }
  340. /// <summary>
  341. /// Display the playlist selector.
  342. /// </summary>
  343. /// <param name="sortMode"></param>
  344. private void onPlaylistButtonClickEvent()
  345. {
  346. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  347. _model.LastSelectedLevelId = null;
  348. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  349. {
  350. PlaylistFlowCoordinator view = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  351. view.didSelectPlaylist += HandleDidSelectPlaylist;
  352. view.Present(_levelSelectionNavigationController);
  353. }
  354. else
  355. {
  356. _model.Settings.filterMode = SongFilterMode.None;
  357. _model.Settings.Save();
  358. UpdateSongList();
  359. RefreshSongList();
  360. }
  361. }
  362. /// <summary>
  363. /// Adjust UI based on level selected.
  364. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  365. /// </summary>
  366. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  367. {
  368. try
  369. {
  370. _log.Trace("OnDidSelectLevelEvent()");
  371. if (level == null)
  372. {
  373. _log.Debug("No level selected?");
  374. return;
  375. }
  376. if (_model.Settings == null)
  377. {
  378. _log.Debug("Settings not instantiated yet?");
  379. return;
  380. }
  381. _model.LastSelectedLevelId = level.levelID;
  382. RefreshAddFavoriteButton(level.levelID);
  383. RefreshQuickScrollButtons();
  384. if (level.levelID.StartsWith("Folder_"))
  385. {
  386. _log.Debug("Folder selected! Adjust PlayButton logic...");
  387. HandleDidSelectFolderRow(level);
  388. }
  389. else
  390. {
  391. HandleDidSelectLevelRow(level);
  392. }
  393. }
  394. catch (Exception e)
  395. {
  396. _log.Exception("Exception selecting song:", e);
  397. }
  398. }
  399. /// <summary>
  400. ///
  401. /// </summary>
  402. private void HandleDidSelectFolderRow(IStandardLevel level)
  403. {
  404. _enterFolderButton.gameObject.SetActive(true);
  405. _playButton.gameObject.SetActive(false);
  406. }
  407. /// <summary>
  408. ///
  409. /// </summary>
  410. /// <param name="level"></param>
  411. private void HandleDidSelectLevelRow(IStandardLevel level)
  412. {
  413. if (_enterFolderButton != null)
  414. {
  415. _enterFolderButton.gameObject.SetActive(false);
  416. }
  417. _playButton.gameObject.SetActive(true);
  418. }
  419. /// <summary>
  420. /// Pop up a delete dialog.
  421. /// </summary>
  422. private void HandleDeleteSelectedLevel()
  423. {
  424. IStandardLevel level = this._levelListViewController.selectedLevel;
  425. if (level == null)
  426. {
  427. _log.Info("No level selected, cannot delete nothing...");
  428. return;
  429. }
  430. if (level.levelID.StartsWith("Level"))
  431. {
  432. _log.Debug("Cannot delete non-custom levels.");
  433. return;
  434. }
  435. if (level.levelID.StartsWith("Folder"))
  436. {
  437. _log.Debug("Cannot delete folders.");
  438. return;
  439. }
  440. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  441. 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");
  442. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  443. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  444. }
  445. /// <summary>
  446. /// Handle delete dialog resolution.
  447. /// </summary>
  448. /// <param name="viewController"></param>
  449. /// <param name="ok"></param>
  450. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  451. {
  452. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  453. if (!ok)
  454. {
  455. viewController.DismissModalViewController(null, false);
  456. }
  457. else
  458. {
  459. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  460. IStandardLevel level = this._levelListViewController.selectedLevel;
  461. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  462. string songPath = customLevel.customSongInfo.path;
  463. bool isZippedSong = false;
  464. viewController.DismissModalViewController(null, false);
  465. _log.Debug("Deleting: {0}", songPath);
  466. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  467. {
  468. isZippedSong = true;
  469. }
  470. if (isZippedSong)
  471. {
  472. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  473. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  474. Directory.Delete(songHashDir.FullName, true);
  475. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  476. {
  477. string hash = CreateMD5FromFile(file);
  478. if (hash != null)
  479. {
  480. if (hash == songHashDir.Name)
  481. {
  482. _log.Debug("Deleting zipped song: {0}", file);
  483. File.Delete(file);
  484. break;
  485. }
  486. }
  487. }
  488. }
  489. else
  490. {
  491. FileAttributes attr = File.GetAttributes(songPath);
  492. if (attr.HasFlag(FileAttributes.Directory))
  493. {
  494. _log.Debug("Deleting song: {0}", songPath);
  495. Directory.Delete(songPath, true);
  496. }
  497. }
  498. _model.LastSelectedLevelId = null;
  499. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  500. this.UpdateSongList();
  501. this.RefreshSongList();
  502. }
  503. }
  504. /// <summary>
  505. /// Create MD5 of a file.
  506. /// </summary>
  507. /// <param name="path"></param>
  508. /// <returns></returns>
  509. public static string CreateMD5FromFile(string path)
  510. {
  511. string hash = "";
  512. if (!File.Exists(path)) return null;
  513. using (MD5 md5 = MD5.Create())
  514. {
  515. using (var stream = File.OpenRead(path))
  516. {
  517. byte[] hashBytes = md5.ComputeHash(stream);
  518. StringBuilder sb = new StringBuilder();
  519. foreach (byte hashByte in hashBytes)
  520. {
  521. sb.Append(hashByte.ToString("X2"));
  522. }
  523. hash = sb.ToString();
  524. return hash;
  525. }
  526. }
  527. }
  528. /// <summary>
  529. /// Handle selection of a playlist. Show just the songs in the playlist.
  530. /// </summary>
  531. /// <param name="p"></param>
  532. private void HandleDidSelectPlaylist(Playlist p)
  533. {
  534. _log.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  535. _model.Settings.filterMode = SongFilterMode.Playlist;
  536. _model.CurrentPlaylist = p;
  537. _model.Settings.Save();
  538. this.UpdateSongList();
  539. this.RefreshSongList();
  540. }
  541. /// <summary>
  542. /// Display the search keyboard
  543. /// </summary>
  544. void ShowSearchKeyboard()
  545. {
  546. if (_searchViewController == null)
  547. {
  548. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  549. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  550. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  551. }
  552. _log.Debug("Presenting keyboard");
  553. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  554. }
  555. /// <summary>
  556. /// Handle back button event from search keyboard.
  557. /// </summary>
  558. private void SearchViewControllerbackButtonPressed()
  559. {
  560. }
  561. /// <summary>
  562. /// Handle search.
  563. /// </summary>
  564. /// <param name="searchFor"></param>
  565. private void SearchViewControllerSearchButtonPressed(string searchFor)
  566. {
  567. _log.Debug("Searching for \"{0}\"...", searchFor);
  568. _model.Settings.searchTerms.Insert(0, searchFor);
  569. _model.Settings.Save();
  570. _model.LastSelectedLevelId = null;
  571. this.UpdateSongList();
  572. this.RefreshSongList();
  573. }
  574. /// <summary>
  575. /// Make big jumps in the song list.
  576. /// </summary>
  577. /// <param name="numJumps"></param>
  578. private void JumpSongList(int numJumps, float segmentPercent)
  579. {
  580. int totalSize = _model.SortedSongList.Count;
  581. int segmentSize = (int)(totalSize * segmentPercent);
  582. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  583. HashSet<int> rows = tableView.GetPrivateField<HashSet<int>>("_selectedRows");
  584. int listSegment = (rows.First() / segmentSize);
  585. int newSegment = listSegment + numJumps;
  586. int newRow = 0;
  587. if (newSegment > 0)
  588. {
  589. newRow = Math.Min(newSegment * segmentSize, totalSize - 1);
  590. }
  591. _log.Debug("ListSegment: {0}, newRow: {1}", listSegment, newRow);
  592. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  593. }
  594. /// <summary>
  595. /// Add/Remove song from favorites depending on if it already exists.
  596. /// </summary>
  597. private void ToggleSongInFavorites()
  598. {
  599. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  600. if (_model.Settings.Favorites.Contains(songInfo.levelID))
  601. {
  602. _log.Info("Remove {0} from favorites", songInfo.songName);
  603. _model.Settings.Favorites.Remove(songInfo.levelID);
  604. }
  605. else
  606. {
  607. _log.Info("Add {0} to favorites", songInfo.songName);
  608. _model.Settings.Favorites.Add(songInfo.levelID);
  609. }
  610. RefreshAddFavoriteButton(songInfo.levelID);
  611. _model.Settings.SaveFavorites();
  612. }
  613. /// <summary>
  614. /// Update interactive state of the quick scroll buttons.
  615. /// </summary>
  616. private void RefreshQuickScrollButtons()
  617. {
  618. // Refresh the fast scroll buttons
  619. _pageUpTenPercent.interactable = _tableViewPageUpButton.interactable;
  620. _pageUpTenPercent.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  621. _pageDownTenPercent.interactable = _tableViewPageDownButton.interactable;
  622. _pageDownTenPercent.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  623. }
  624. /// <summary>
  625. /// Helper to quickly refresh add to favorites button
  626. /// </summary>
  627. /// <param name="levelId"></param>
  628. private void RefreshAddFavoriteButton(String levelId)
  629. {
  630. if (levelId == null)
  631. {
  632. _currentAddFavoriteButtonSprite = null;
  633. }
  634. else
  635. {
  636. if (_model.Settings.Favorites.Contains(levelId))
  637. {
  638. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  639. }
  640. else
  641. {
  642. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  643. }
  644. }
  645. UIBuilder.SetButtonIcon(ref _addFavoriteButton, _currentAddFavoriteButtonSprite);
  646. }
  647. /// <summary>
  648. /// Adjust the UI colors.
  649. /// </summary>
  650. public void RefreshSortButtonUI()
  651. {
  652. // So far all we need to refresh is the sort buttons.
  653. foreach (SongSortButton sortButton in _sortButtonGroup)
  654. {
  655. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  656. if (sortButton.SortMode == _model.Settings.sortMode)
  657. {
  658. if (_model.InvertingResults)
  659. {
  660. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  661. }
  662. else
  663. {
  664. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  665. }
  666. }
  667. }
  668. // refresh filter buttons
  669. foreach (SongFilterButton filterButton in _filterButtonGroup)
  670. {
  671. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.clear);
  672. if (filterButton.FilterMode == _model.Settings.filterMode)
  673. {
  674. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.green);
  675. }
  676. }
  677. }
  678. /// <summary>
  679. /// Refresh the UI state of any directory buttons.
  680. /// </summary>
  681. public void RefreshDirectoryButtons()
  682. {
  683. // bail if no button, likely folder support not enabled.
  684. if (_upFolderButton == null)
  685. {
  686. return;
  687. }
  688. if (_model.DirStackSize > 1)
  689. {
  690. _upFolderButton.interactable = true;
  691. }
  692. else
  693. {
  694. _upFolderButton.interactable = false;
  695. }
  696. }
  697. /// <summary>
  698. /// Try to refresh the song list. Broken for now.
  699. /// </summary>
  700. public void RefreshSongList()
  701. {
  702. _log.Info("Refreshing the song list view.");
  703. try
  704. {
  705. if (_model.SortedSongList == null)
  706. {
  707. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  708. return;
  709. }
  710. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  711. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  712. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  713. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  714. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  715. tableView.ReloadData();
  716. String selectedLevelID = null;
  717. if (_model.LastSelectedLevelId != null)
  718. {
  719. selectedLevelID = _model.LastSelectedLevelId;
  720. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  721. }
  722. else
  723. {
  724. if (levels.Length > 0)
  725. {
  726. selectedLevelID = levels.FirstOrDefault().levelID;
  727. }
  728. }
  729. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  730. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  731. {
  732. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  733. }
  734. RefreshSortButtonUI();
  735. RefreshQuickScrollButtons();
  736. }
  737. catch (Exception e)
  738. {
  739. _log.Exception("Exception refreshing song list:", e);
  740. }
  741. }
  742. /// <summary>
  743. /// Scroll TableView to proper row, fire events.
  744. /// </summary>
  745. /// <param name="table"></param>
  746. /// <param name="levelID"></param>
  747. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  748. {
  749. int row = table.RowNumberForLevelID(levelID);
  750. TableView tableView = table.GetComponentInChildren<TableView>();
  751. tableView.SelectRow(row, true);
  752. tableView.ScrollToRow(row, true);
  753. _lastRow = row;
  754. }
  755. /// <summary>
  756. /// Helper for updating the model (which updates the song list)c
  757. /// </summary>
  758. public void UpdateSongList()
  759. {
  760. _log.Trace("UpdateSongList()");
  761. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  762. _model.UpdateSongLists(gameplayMode);
  763. this.RefreshDirectoryButtons();
  764. }
  765. /// <summary>
  766. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  767. /// </summary>
  768. public void LateUpdate()
  769. {
  770. CheckDebugUserInput();
  771. }
  772. /// <summary>
  773. /// Map some key presses directly to UI interactions to make testing easier.
  774. /// </summary>
  775. private void CheckDebugUserInput()
  776. {
  777. try
  778. {
  779. if (this._levelListViewController != null && this._levelListViewController.isActiveAndEnabled)
  780. {
  781. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  782. // back
  783. if (Input.GetKeyDown(KeyCode.Escape))
  784. {
  785. this._levelSelectionNavigationController.DismissButtonWasPressed();
  786. }
  787. // cycle sort modes
  788. if (Input.GetKeyDown(KeyCode.T))
  789. {
  790. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  791. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  792. }
  793. // select current sort mode again (toggle inverting)
  794. if (Input.GetKeyDown(KeyCode.Y))
  795. {
  796. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  797. }
  798. // filter playlists
  799. if (Input.GetKeyDown(KeyCode.P))
  800. {
  801. onPlaylistButtonClickEvent();
  802. }
  803. // filter search
  804. if (Input.GetKeyDown(KeyCode.S))
  805. {
  806. onSearchButtonClickEvent();
  807. }
  808. // filter favorites
  809. if (Input.GetKeyDown(KeyCode.F))
  810. {
  811. onFavoriteFilterButtonClickEvent();
  812. }
  813. // delete
  814. if (Input.GetKeyDown(KeyCode.D))
  815. {
  816. _deleteButton.onClick.Invoke();
  817. }
  818. // c - select difficulty for top song
  819. if (Input.GetKeyDown(KeyCode.C))
  820. {
  821. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  822. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  823. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  824. }
  825. // v start a song or enter a folder
  826. if (Input.GetKeyDown(KeyCode.Return))
  827. {
  828. if (_playButton.isActiveAndEnabled)
  829. {
  830. _playButton.onClick.Invoke();
  831. }
  832. else if (_enterFolderButton.isActiveAndEnabled)
  833. {
  834. _enterFolderButton.onClick.Invoke();
  835. }
  836. }
  837. // backspace - up a folder
  838. if (Input.GetKeyDown(KeyCode.Backspace))
  839. {
  840. _upFolderButton.onClick.Invoke();
  841. }
  842. // change song index
  843. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  844. {
  845. _pageUpTenPercent.onClick.Invoke();
  846. }
  847. else if (Input.GetKeyDown(KeyCode.N))
  848. {
  849. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  850. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  851. }
  852. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  853. {
  854. _pageDownTenPercent.onClick.Invoke();
  855. }
  856. else if (Input.GetKeyDown(KeyCode.M))
  857. {
  858. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  859. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  860. }
  861. // add to favorites
  862. if (Input.GetKeyDown(KeyCode.KeypadPlus))
  863. {
  864. ToggleSongInFavorites();
  865. }
  866. }
  867. else if (_deleteDialog != null && _deleteDialog.isInViewControllerHierarchy)
  868. {
  869. // accept delete
  870. if (Input.GetKeyDown(KeyCode.Return))
  871. {
  872. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  873. }
  874. if (Input.GetKeyDown(KeyCode.Escape))
  875. {
  876. _deleteDialog.GetPrivateField<TextMeshProButton>("_cancelButton").button.onClick.Invoke();
  877. }
  878. }
  879. }
  880. catch (Exception e)
  881. {
  882. _log.Exception("Debug Input caused Exception: ", e);
  883. }
  884. }
  885. }
  886. }