SongBrowserUI.cs 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using UnityEngine.UI;
  7. using HMUI;
  8. using VRUI;
  9. using SongBrowserPlugin.DataAccess;
  10. using System.IO;
  11. using SongLoaderPlugin;
  12. using System.Security.Cryptography;
  13. using System.Text;
  14. using System.Text.RegularExpressions;
  15. using TMPro;
  16. using Logger = SongBrowserPlugin.Logging.Logger;
  17. using SongBrowserPlugin.DataAccess.BeatSaverApi;
  18. using CustomUI.BeatSaber;
  19. namespace SongBrowserPlugin.UI
  20. {
  21. /// <summary>
  22. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  23. /// </summary>
  24. public class SongBrowserUI : MonoBehaviour
  25. {
  26. // Logging
  27. public const String Name = "SongBrowserUI";
  28. private const float SEGMENT_PERCENT = 0.1f;
  29. private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
  30. // Beat Saber UI Elements
  31. private FlowCoordinator _levelSelectionFlowCoordinator;
  32. private LevelListViewController _levelListViewController;
  33. private StandardLevelDetailViewController _levelDetailViewController;
  34. private BeatmapDifficultyViewController _levelDifficultyViewController;
  35. private BeatmapCharacteristicSelectionViewController _beatmapCharacteristicSelectionViewController;
  36. private DismissableNavigationController _levelSelectionNavigationController;
  37. private LevelListTableView _levelListTableView;
  38. private RectTransform _tableViewRectTransform;
  39. private Button _tableViewPageUpButton;
  40. private Button _tableViewPageDownButton;
  41. private Button _playButton;
  42. // New UI Elements
  43. private List<SongSortButton> _sortButtonGroup;
  44. private List<SongFilterButton> _filterButtonGroup;
  45. private Button _addFavoriteButton;
  46. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  47. private SimpleDialogPromptViewController _deleteDialog;
  48. private Button _deleteButton;
  49. private Button _pageUpFastButton;
  50. private Button _pageDownFastButton;
  51. private Button _enterFolderButton;
  52. private Button _upFolderButton;
  53. private SearchKeyboardViewController _searchViewController;
  54. private PlaylistFlowCoordinator _playListFlowCoordinator;
  55. private RectTransform _ppStatButton;
  56. private RectTransform _starStatButton;
  57. private RectTransform _njsStatButton;
  58. // Cached items
  59. private Sprite _addFavoriteSprite;
  60. private Sprite _removeFavoriteSprite;
  61. private Sprite _currentAddFavoriteButtonSprite;
  62. // Plugin Compat checks
  63. private bool _detectedTwitchPluginQueue = false;
  64. private bool _checkedForTwitchPlugin = false;
  65. // Debug
  66. private int _sortButtonLastPushedIndex = 0;
  67. private int _lastRow = 0;
  68. // Model
  69. private SongBrowserModel _model;
  70. // UI Created
  71. private bool _rebuildUI = true;
  72. public SongBrowserModel Model
  73. {
  74. get
  75. {
  76. return _model;
  77. }
  78. }
  79. /// <summary>
  80. /// Constructor
  81. /// </summary>
  82. public SongBrowserUI() : base()
  83. {
  84. if (_model == null)
  85. {
  86. _model = new SongBrowserModel();
  87. }
  88. _model.Init();
  89. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  90. }
  91. /// <summary>
  92. /// Builds the UI for this plugin.
  93. /// </summary>
  94. public void CreateUI(MainMenuViewController.MenuButton mode)
  95. {
  96. Logger.Trace("CreateUI()");
  97. var soloFlow = Resources.FindObjectsOfTypeAll<SoloFreePlayFlowCoordinator>().First();
  98. var partyFlow = Resources.FindObjectsOfTypeAll<PartyFreePlayFlowCoordinator>().First();
  99. if (mode == MainMenuViewController.MenuButton.SoloFreePlay)
  100. {
  101. _levelSelectionFlowCoordinator = soloFlow;
  102. }
  103. else
  104. {
  105. _levelSelectionFlowCoordinator = partyFlow;
  106. }
  107. // returning to the menu and switching modes could trigger this.
  108. if (!_rebuildUI)
  109. {
  110. return;
  111. }
  112. try
  113. {
  114. // gather controllers and ui elements.
  115. if (_levelListViewController == null)
  116. {
  117. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<LevelListViewController>("_levelListViewController");
  118. }
  119. if (_levelDetailViewController == null)
  120. {
  121. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  122. }
  123. if (_beatmapCharacteristicSelectionViewController == null)
  124. {
  125. _beatmapCharacteristicSelectionViewController = _levelSelectionFlowCoordinator.GetPrivateField<BeatmapCharacteristicSelectionViewController>("_beatmapCharacteristicSelectionViewController");
  126. }
  127. if (_levelSelectionNavigationController == null)
  128. {
  129. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<DismissableNavigationController>("_navigationController");
  130. }
  131. if (_levelDifficultyViewController == null)
  132. {
  133. _levelDifficultyViewController = soloFlow.GetPrivateField<BeatmapDifficultyViewController>("_beatmapDifficultyViewControllerViewController");
  134. }
  135. if (_levelListTableView == null)
  136. {
  137. _levelListTableView = this._levelListViewController.GetComponentInChildren<LevelListTableView>();
  138. }
  139. _playButton = _levelDetailViewController.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  140. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  141. if (_playListFlowCoordinator == null)
  142. {
  143. _playListFlowCoordinator = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  144. _playListFlowCoordinator.didFinishEvent += HandleDidSelectPlaylist;
  145. }
  146. // delete dialog
  147. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  148. this._deleteDialog.name = "DeleteDialogPromptViewController";
  149. this._deleteDialog.gameObject.SetActive(false);
  150. // sprites
  151. this._addFavoriteSprite = Base64Sprites.AddToFavoritesIcon;
  152. this._removeFavoriteSprite = Base64Sprites.RemoveFromFavoritesIcon;
  153. // create song browser main ui
  154. this.CreateUIElements();
  155. // handlers
  156. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  157. tableView.didSelectRowEvent += HandleDidSelectTableViewRow;
  158. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  159. _levelDifficultyViewController.didSelectDifficultyEvent += OnDidSelectDifficultyEvent;
  160. _beatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent += OnDidSelectBeatmapCharacteristic;
  161. // modify details view
  162. var statsPanel = this._levelDetailViewController.GetComponentsInChildren<CanvasRenderer>(true).First(x => x.name == "LevelParamsPanel");
  163. var statTransforms = statsPanel.GetComponentsInChildren<RectTransform>();
  164. var valueTexts = statsPanel.GetComponentsInChildren<TextMeshProUGUI>().Where(x => x.name == "ValueText").ToList();
  165. RectTransform panelRect = (statsPanel.transform as RectTransform);
  166. panelRect.sizeDelta = new Vector2(panelRect.sizeDelta.x * 1.2f, panelRect.sizeDelta.y * 1.2f);
  167. for (int i = 0; i < statTransforms.Length; i++)
  168. {
  169. var r = statTransforms[i];
  170. if (r.name == "Separator")
  171. {
  172. continue;
  173. }
  174. r.sizeDelta = new Vector2(r.sizeDelta.x * 0.75f, r.sizeDelta.y * 0.75f);
  175. }
  176. for (int i = 0; i < valueTexts.Count; i++)
  177. {
  178. var text = valueTexts[i];
  179. text.fontSize = 3.25f;
  180. }
  181. _ppStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  182. UIBuilder.SetStatButtonIcon(_ppStatButton, Base64Sprites.GraphIcon);
  183. _starStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  184. UIBuilder.SetStatButtonIcon(_starStatButton, Base64Sprites.StarIcon);
  185. _njsStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  186. UIBuilder.SetStatButtonIcon(_njsStatButton, Base64Sprites.SpeedIcon);
  187. // shrink title
  188. var titleText = this._levelDetailViewController.GetComponentsInChildren<TextMeshProUGUI>(true).First(x => x.name == "SongNameText");
  189. titleText.fontSize = 5.0f;
  190. _rebuildUI = false;
  191. }
  192. catch (Exception e)
  193. {
  194. Logger.Exception("Exception during CreateUI: ", e);
  195. }
  196. }
  197. /// <summary>
  198. /// Builds the SongBrowser UI
  199. /// </summary>
  200. private void CreateUIElements()
  201. {
  202. Logger.Trace("CreateUIElements");
  203. try
  204. {
  205. // Gather some transforms and templates to use.
  206. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  207. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  208. Button sortButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "SettingsButton"));
  209. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "SettingsButton"));
  210. Button practiceButton = Resources.FindObjectsOfTypeAll<Button>().First(x => x.name == "PracticeButton");
  211. RectTransform practiceButtonRect = (practiceButton.transform as RectTransform);
  212. Button playButton = Resources.FindObjectsOfTypeAll<Button>().First(x => x.name == "PlayButton");
  213. RectTransform playButtonRect = (playButton.transform as RectTransform);
  214. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  215. Sprite borderSprite = SongBrowserApplication.Instance.CachedIcons["RoundRectBigStroke"];
  216. // Resize some of the UI
  217. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  218. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  219. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  220. // Create Sorting Songs By-Buttons
  221. Logger.Debug("Creating sort by buttons...");
  222. float buttonSpacing = 0.5f;
  223. float fontSize = 2.0f;
  224. float buttonWidth = 12.25f;
  225. float buttonHeight = 5.5f;
  226. float startButtonX = 24.50f;
  227. float curButtonX = 0.0f;
  228. float buttonY = -6.0f;
  229. string[] sortButtonNames = new string[]
  230. {
  231. "Song", "Author", "Original", "Newest", "Plays", "PP", "Difficult", "Random"
  232. };
  233. SongSortMode[] sortModes = new SongSortMode[]
  234. {
  235. SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.PP, SongSortMode.Difficulty, SongSortMode.Random
  236. };
  237. _sortButtonGroup = new List<SongSortButton>();
  238. for (int i = 0; i < sortButtonNames.Length; i++)
  239. {
  240. curButtonX = startButtonX + (buttonWidth*i) + (buttonSpacing*i);
  241. SongSortButton newButton = UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon, borderSprite,
  242. sortButtonNames[i],
  243. fontSize,
  244. curButtonX,
  245. buttonY,
  246. buttonWidth,
  247. buttonHeight,
  248. sortModes[i],
  249. OnSortButtonClickEvent);
  250. _sortButtonGroup.Add(newButton);
  251. newButton.Button.name = "Sort" + sortModes[i].ToString() + "Button";
  252. }
  253. // Create filter buttons
  254. Logger.Debug("Creating filter buttons...");
  255. float filterButtonX = curButtonX + (buttonWidth / 2.0f);
  256. Vector2 iconButtonSize = new Vector2(buttonHeight, buttonHeight);
  257. List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
  258. {
  259. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, OnFavoriteFilterButtonClickEvent, Base64Sprites.StarFullIcon),
  260. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, OnPlaylistButtonClickEvent, Base64Sprites.PlaylistIcon),
  261. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, OnSearchButtonClickEvent, Base64Sprites.SearchIcon),
  262. };
  263. _filterButtonGroup = new List<SongFilterButton>();
  264. for (int i = 0; i < filterButtonSetup.Count; i++)
  265. {
  266. Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
  267. Button b = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate,
  268. t.Item3,
  269. new Vector2(filterButtonX + (iconButtonSize.x * i) + (buttonSpacing * i), buttonY),
  270. new Vector2(iconButtonSize.x, iconButtonSize.y),
  271. new Vector2(3.5f, 3.5f),
  272. new Vector2(1.0f, 1.0f),
  273. 0);
  274. SongFilterButton filterButton = new SongFilterButton
  275. {
  276. Button = b,
  277. FilterMode = t.Item1
  278. };
  279. b.onClick.AddListener(t.Item2);
  280. filterButton.Button.name = "Filter" + t.Item1.ToString() + "Button";
  281. _filterButtonGroup.Add(filterButton);
  282. }
  283. // Get element info to position properly
  284. RectTransform detailContainerRect = _levelDetailViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "Container");
  285. RectTransform detailButtonRect = _levelDetailViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "Buttons");
  286. detailButtonRect.anchoredPosition = new Vector2(detailButtonRect.anchoredPosition.x, detailButtonRect.anchoredPosition.y + 5.0f);
  287. // clone existing button group
  288. RectTransform newButtonRect = UnityEngine.Object.Instantiate(detailButtonRect, detailContainerRect, false);
  289. newButtonRect.name = "Buttons2";
  290. newButtonRect.anchoredPosition = new Vector2(newButtonRect.anchoredPosition.x, newButtonRect.anchoredPosition.y - 10.0f);
  291. // Create add favorite button
  292. _addFavoriteButton = newButtonRect.GetComponentsInChildren<Button>().First(x => x.name == "PracticeButton");
  293. _addFavoriteButton.name = "AddFavoritesButton";
  294. _addFavoriteButton.onClick.RemoveAllListeners();
  295. (_addFavoriteButton.transform as RectTransform).sizeDelta = new Vector2(practiceButtonRect.sizeDelta.x, practiceButtonRect.sizeDelta.y);
  296. UnityEngine.UI.Image icon = _addFavoriteButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(c => c.name == "Icon");
  297. RectTransform iconTransform = icon.rectTransform;
  298. iconTransform.localScale = new Vector2(0.75f, 0.75f);
  299. _addFavoriteButton.GetComponentsInChildren<HorizontalLayoutGroup>().First(c => c.name == "Content").padding = new RectOffset(1, 1, 0, 0);
  300. _addFavoriteButton.onClick.AddListener(delegate () {
  301. ToggleSongInPlaylist();
  302. });
  303. // Create delete button
  304. _deleteButton = newButtonRect.GetComponentsInChildren<Button>().First(x => x.name == "PlayButton");
  305. _deleteButton.name = "DeleteButton";
  306. _deleteButton.onClick.RemoveAllListeners();
  307. _deleteButton.GetComponentsInChildren<HorizontalLayoutGroup>().First(c => c.name == "Content").padding = new RectOffset(7, 7, 0, 0);
  308. UIBuilder.SetButtonText(_deleteButton, "Delete");
  309. _deleteButton.onClick.AddListener(delegate () {
  310. HandleDeleteSelectedLevel();
  311. });
  312. // Create fast scroll buttons
  313. _pageUpFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  314. new Vector2(32, -12),
  315. new Vector2(6.0f, 5.5f),
  316. new Vector2(1.5f, 1.5f),
  317. new Vector2(1.0f, 1.0f),
  318. 180);
  319. UnityEngine.GameObject.Destroy(_pageUpFastButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(btn => btn.name == "Stroke"));
  320. _pageUpFastButton.onClick.AddListener(delegate () {
  321. this.JumpSongList(-1, SEGMENT_PERCENT);
  322. });
  323. _pageDownFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  324. new Vector2(32, -78.5f),
  325. new Vector2(6.0f, 5.5f),
  326. new Vector2(1.5f, 1.5f),
  327. new Vector2(1.0f, 1.0f),
  328. 0);
  329. UnityEngine.GameObject.Destroy(_pageDownFastButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(btn => btn.name == "Stroke"));
  330. _pageDownFastButton.onClick.AddListener(delegate () {
  331. this.JumpSongList(1, SEGMENT_PERCENT);
  332. });
  333. // Create enter folder button
  334. if (_model.Settings.folderSupportEnabled)
  335. {
  336. _enterFolderButton = UIBuilder.CreateUIButton(otherButtonTransform, _playButton);
  337. _enterFolderButton.onClick.AddListener(delegate ()
  338. {
  339. _model.PushDirectory(_levelListViewController.selectedLevel);
  340. this.RefreshSongList();
  341. this.RefreshDirectoryButtons();
  342. });
  343. UIBuilder.SetButtonText(_enterFolderButton, "Enter");
  344. // Create up folder button
  345. _upFolderButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  346. new Vector2(filterButtonX + (iconButtonSize.x* filterButtonSetup.Count), buttonY),
  347. new Vector2(iconButtonSize.x, iconButtonSize.y),
  348. new Vector2(0.85f, 0.85f),
  349. new Vector2(2.0f, 2.0f),
  350. 180);
  351. _upFolderButton.onClick.RemoveAllListeners();
  352. _upFolderButton.onClick.AddListener(delegate ()
  353. {
  354. _model.PopDirectory();
  355. this.RefreshSongList();
  356. this.RefreshDirectoryButtons();
  357. });
  358. }
  359. RefreshSortButtonUI();
  360. RefreshDirectoryButtons();
  361. }
  362. catch (Exception e)
  363. {
  364. Logger.Exception("Exception CreateUIElements:", e);
  365. }
  366. }
  367. /// <summary>
  368. /// Sort button clicked.
  369. /// </summary>
  370. private void OnSortButtonClickEvent(SongSortMode sortMode)
  371. {
  372. Logger.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  373. _model.LastSelectedLevelId = null;
  374. if (_model.Settings.sortMode == sortMode)
  375. {
  376. _model.ToggleInverting();
  377. }
  378. _model.Settings.sortMode = sortMode;
  379. _model.Settings.Save();
  380. // update the seed
  381. if (_model.Settings.sortMode == SongSortMode.Random)
  382. {
  383. this.Model.Settings.randomSongSeed = Guid.NewGuid().GetHashCode();
  384. this.Model.Settings.Save();
  385. }
  386. UpdateSongList();
  387. RefreshSongList();
  388. // Handle instant queue logic, avoid picking a folder.
  389. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  390. {
  391. for (int i = 0; i < _model.SortedSongList.Count; i++)
  392. {
  393. if (!_model.SortedSongList[i].levelID.StartsWith("Folder_"))
  394. {
  395. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[i].levelID);
  396. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, _model.SortedSongList[i].difficultyBeatmaps.Length-1);
  397. _playButton.onClick.Invoke();
  398. break;
  399. }
  400. }
  401. }
  402. }
  403. /// <summary>
  404. /// Filter by favorites.
  405. /// </summary>
  406. private void OnFavoriteFilterButtonClickEvent()
  407. {
  408. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
  409. if (_model.Settings.filterMode != SongFilterMode.Favorites)
  410. {
  411. _model.Settings.filterMode = SongFilterMode.Favorites;
  412. }
  413. else
  414. {
  415. _model.Settings.filterMode = SongFilterMode.None;
  416. }
  417. _model.Settings.Save();
  418. UpdateSongList();
  419. RefreshSongList();
  420. }
  421. /// <summary>
  422. /// Filter button clicked.
  423. /// </summary>
  424. /// <param name="sortMode"></param>
  425. private void OnSearchButtonClickEvent()
  426. {
  427. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  428. if (_model.Settings.filterMode != SongFilterMode.Search)
  429. {
  430. this.ShowSearchKeyboard();
  431. }
  432. else
  433. {
  434. _model.Settings.filterMode = SongFilterMode.None;
  435. UpdateSongList();
  436. RefreshSongList();
  437. }
  438. _model.Settings.Save();
  439. }
  440. /// <summary>
  441. /// Display the playlist selector.
  442. /// </summary>
  443. /// <param name="sortMode"></param>
  444. private void OnPlaylistButtonClickEvent()
  445. {
  446. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  447. _model.LastSelectedLevelId = null;
  448. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  449. {
  450. _playListFlowCoordinator.ParentFlowCoordinator = _levelSelectionFlowCoordinator;
  451. _levelSelectionFlowCoordinator.InvokePrivateMethod("PresentFlowCoordinator", new object[] { _playListFlowCoordinator, null, false, false });
  452. }
  453. else
  454. {
  455. _model.Settings.filterMode = SongFilterMode.None;
  456. _model.Settings.Save();
  457. UpdateSongList();
  458. RefreshSongList();
  459. }
  460. }
  461. /// <summary>
  462. /// Adjust UI based on level selected.
  463. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  464. /// </summary>
  465. private void OnDidSelectLevelEvent(LevelListViewController view, IBeatmapLevel level)
  466. {
  467. try
  468. {
  469. Logger.Trace("OnDidSelectLevelEvent()");
  470. if (level == null)
  471. {
  472. Logger.Debug("No level selected?");
  473. return;
  474. }
  475. if (_model.Settings == null)
  476. {
  477. Logger.Debug("Settings not instantiated yet?");
  478. return;
  479. }
  480. _model.LastSelectedLevelId = level.levelID;
  481. RefreshAddFavoriteButton(level.levelID);
  482. RefreshQuickScrollButtons();
  483. if (level.levelID.StartsWith("Folder_"))
  484. {
  485. Logger.Debug("Folder selected! Adjust PlayButton logic...");
  486. HandleDidSelectFolderRow(level);
  487. }
  488. else
  489. {
  490. HandleDidSelectLevelRow(level);
  491. }
  492. }
  493. catch (Exception e)
  494. {
  495. Logger.Exception("Exception selecting song:", e);
  496. }
  497. }
  498. /// <summary>
  499. /// Switching one-saber modes for example.
  500. /// </summary>
  501. /// <param name="view"></param>
  502. /// <param name="bc"></param>
  503. private void OnDidSelectBeatmapCharacteristic(BeatmapCharacteristicSelectionViewController view, BeatmapCharacteristicSO bc)
  504. {
  505. Logger.Trace("OnDidSelectBeatmapCharacteristic({0}", bc.name);
  506. _model.UpdateSongLists(bc);
  507. this.RefreshSongList();
  508. }
  509. /// <summary>
  510. /// Handle difficulty level selection.
  511. /// </summary>
  512. private void OnDidSelectDifficultyEvent(BeatmapDifficultyViewController view, IDifficultyBeatmap beatmap)
  513. {
  514. _deleteButton.interactable = (beatmap.level.levelID.Length >= 32);
  515. this.RefreshScoreSaberData(_levelListViewController.selectedLevel);
  516. }
  517. /// <summary>
  518. /// Turn play button into enter folder button.
  519. /// </summary>
  520. private void HandleDidSelectFolderRow(IBeatmapLevel level)
  521. {
  522. _enterFolderButton.gameObject.SetActive(true);
  523. _playButton.gameObject.SetActive(false);
  524. }
  525. /// <summary>
  526. /// Turn enter folder button into play button.
  527. /// </summary>
  528. /// <param name="level"></param>
  529. private void HandleDidSelectLevelRow(IBeatmapLevel level)
  530. {
  531. // deal with enter folder button
  532. if (_enterFolderButton != null)
  533. {
  534. _enterFolderButton.gameObject.SetActive(false);
  535. }
  536. _playButton.gameObject.SetActive(true);
  537. this.RefreshScoreSaberData(level);
  538. }
  539. /// <summary>
  540. /// Track the current row.
  541. /// </summary>
  542. /// <param name="tableView"></param>
  543. /// <param name="row"></param>
  544. private void HandleDidSelectTableViewRow(TableView tableView, int row)
  545. {
  546. Logger.Trace("HandleDidSelectTableViewRow({0})", row);
  547. _lastRow = row;
  548. }
  549. /// <summary>
  550. /// Pop up a delete dialog.
  551. /// </summary>
  552. private void HandleDeleteSelectedLevel()
  553. {
  554. IBeatmapLevel level = this._levelListViewController.selectedLevel;
  555. if (level == null)
  556. {
  557. Logger.Info("No level selected, cannot delete nothing...");
  558. return;
  559. }
  560. if (!_model.LevelIdToCustomSongInfos.ContainsKey(level.levelID))
  561. {
  562. Logger.Debug("Cannot delete non-custom levels.");
  563. return;
  564. }
  565. if (level.levelID.StartsWith("Folder"))
  566. {
  567. Logger.Debug("Cannot delete folders.");
  568. return;
  569. }
  570. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  571. 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");
  572. this._deleteDialog.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  573. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  574. _levelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { this._deleteDialog, null, false });
  575. }
  576. /// <summary>
  577. /// Handle delete dialog resolution.
  578. /// </summary>
  579. /// <param name="viewController"></param>
  580. /// <param name="ok"></param>
  581. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  582. {
  583. _levelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _deleteDialog, null, false });
  584. if (ok)
  585. {
  586. Downloader.Instance.DeleteSong(new Song(SongLoader.CustomLevels.First(x => x.levelID == _levelDetailViewController.difficultyBeatmap.level.levelID)));
  587. List<IBeatmapLevel> levels = _levelListViewController.GetPrivateField<IBeatmapLevel[]>("_levels").ToList();
  588. int selectedIndex = levels.IndexOf(_levelDetailViewController.difficultyBeatmap.level);
  589. if (selectedIndex > -1)
  590. {
  591. levels.Remove(_levelDetailViewController.difficultyBeatmap.level);
  592. if (selectedIndex > 0)
  593. {
  594. selectedIndex--;
  595. }
  596. _levelListViewController.SetLevels(levels.ToArray());
  597. TableView listTableView = _levelListViewController.GetPrivateField<LevelListTableView>("_levelListTableView").GetPrivateField<TableView>("_tableView");
  598. listTableView.ScrollToRow(selectedIndex, false);
  599. listTableView.SelectRow(selectedIndex, true);
  600. }
  601. this.UpdateSongList();
  602. this.RefreshSongList();
  603. }
  604. }
  605. /// <summary>
  606. /// Create MD5 of a file.
  607. /// </summary>
  608. /// <param name="path"></param>
  609. /// <returns></returns>
  610. public static string CreateMD5FromFile(string path)
  611. {
  612. string hash = "";
  613. if (!File.Exists(path)) return null;
  614. using (MD5 md5 = MD5.Create())
  615. {
  616. using (var stream = File.OpenRead(path))
  617. {
  618. byte[] hashBytes = md5.ComputeHash(stream);
  619. StringBuilder sb = new StringBuilder();
  620. foreach (byte hashByte in hashBytes)
  621. {
  622. sb.Append(hashByte.ToString("X2"));
  623. }
  624. hash = sb.ToString();
  625. return hash;
  626. }
  627. }
  628. }
  629. /// <summary>
  630. /// Handle selection of a playlist. Show just the songs in the playlist.
  631. /// </summary>
  632. /// <param name="p"></param>
  633. private void HandleDidSelectPlaylist(Playlist p)
  634. {
  635. if (p != null)
  636. {
  637. Logger.Debug("Showing songs for playlist: {0}", p.Title);
  638. _model.Settings.filterMode = SongFilterMode.Playlist;
  639. _model.CurrentPlaylist = p;
  640. _model.Settings.Save();
  641. this.UpdateSongList();
  642. this.RefreshSongList();
  643. this.RefreshSortButtonUI();
  644. }
  645. else
  646. {
  647. Logger.Debug("No playlist selected");
  648. }
  649. }
  650. /// <summary>
  651. /// Display the search keyboard
  652. /// </summary>
  653. void ShowSearchKeyboard()
  654. {
  655. if (_searchViewController == null)
  656. {
  657. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  658. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  659. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  660. }
  661. Logger.Debug("Presenting search keyboard");
  662. _levelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _searchViewController, null, false });
  663. }
  664. /// <summary>
  665. /// Handle back button event from search keyboard.
  666. /// </summary>
  667. private void SearchViewControllerbackButtonPressed()
  668. {
  669. _levelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
  670. // force disable search filter.
  671. this._model.Settings.filterMode = SongFilterMode.None;
  672. this._model.Settings.Save();
  673. }
  674. /// <summary>
  675. /// Handle search.
  676. /// </summary>
  677. /// <param name="searchFor"></param>
  678. private void SearchViewControllerSearchButtonPressed(string searchFor)
  679. {
  680. _levelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
  681. Logger.Debug("Searching for \"{0}\"...", searchFor);
  682. _model.Settings.filterMode = SongFilterMode.Search;
  683. _model.Settings.searchTerms.Insert(0, searchFor);
  684. _model.Settings.Save();
  685. _model.LastSelectedLevelId = null;
  686. this.UpdateSongList();
  687. this.RefreshSongList();
  688. }
  689. /// <summary>
  690. /// Make big jumps in the song list.
  691. /// </summary>
  692. /// <param name="numJumps"></param>
  693. private void JumpSongList(int numJumps, float segmentPercent)
  694. {
  695. int totalSize = _model.SortedSongList.Count;
  696. int segmentSize = (int)(totalSize * segmentPercent);
  697. // Jump at least one scree size.
  698. if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
  699. {
  700. segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
  701. }
  702. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  703. int jumpDirection = Math.Sign(numJumps);
  704. int newRow = _lastRow + (jumpDirection * segmentSize);
  705. if (newRow <= 0)
  706. {
  707. newRow = 0;
  708. }
  709. else if (newRow >= totalSize)
  710. {
  711. newRow = totalSize - 1;
  712. }
  713. Logger.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  714. _lastRow = newRow;
  715. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  716. }
  717. /// <summary>
  718. /// Add/Remove song from favorites depending on if it already exists.
  719. /// </summary>
  720. private void ToggleSongInPlaylist()
  721. {
  722. IBeatmapLevel songInfo = this._levelListViewController.selectedLevel;
  723. if (_model.CurrentEditingPlaylist != null)
  724. {
  725. if (_model.CurrentEditingPlaylistLevelIds.Contains(songInfo.levelID))
  726. {
  727. Logger.Info("Remove {0} from editing playlist", songInfo.songName);
  728. _model.RemoveSongFromEditingPlaylist(songInfo);
  729. }
  730. else
  731. {
  732. Logger.Info("Add {0} to editing playlist", songInfo.songName);
  733. _model.AddSongToEditingPlaylist(songInfo);
  734. }
  735. }
  736. RefreshAddFavoriteButton(songInfo.levelID);
  737. _model.Settings.Save();
  738. }
  739. /// <summary>
  740. /// Update GUI elements that show score saber data.
  741. /// </summary>
  742. public void RefreshScoreSaberData(IBeatmapLevel level)
  743. {
  744. // TODO - fix this obvious mess...
  745. // use controllers level...
  746. if (level == null)
  747. {
  748. level = _levelListViewController.selectedLevel;
  749. }
  750. // abort!
  751. if (level == null)
  752. {
  753. Logger.Debug("Aborting RefreshScoreSaberData()");
  754. return;
  755. }
  756. Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
  757. // display pp potentially
  758. if (this._model.LevelIdToScoreSaberData != null && this._levelDifficultyViewController.selectedDifficultyBeatmap != null)
  759. {
  760. /*if (this._ppText == null)
  761. {
  762. // Create the PP and Star rating labels
  763. //RectTransform bmpTextRect = Resources.FindObjectsOfTypeAll<RectTransform>().First(x => x.name == "BPMText");
  764. var text = BeatSaberUI.CreateText(this._levelDetailViewController.rectTransform, "PP", new Vector2(-15, -32), new Vector2(10f, 6f));
  765. text.fontSize = 2.5f;
  766. text.alignment = TextAlignmentOptions.Left;
  767. text = BeatSaberUI.CreateText(this._levelDetailViewController.rectTransform, "STAR", new Vector2(-15, -34.5f), new Vector2(10f, 6f));
  768. text.fontSize = 2.5f;
  769. text.alignment = TextAlignmentOptions.Left;
  770. _ppText = BeatSaberUI.CreateText(this._levelDetailViewController.rectTransform, "?", new Vector2(-20, -32), new Vector2(20f, 6f));
  771. _ppText.fontSize = 2.5f;
  772. _ppText.alignment = TextAlignmentOptions.Right;
  773. _starText = BeatSaberUI.CreateText(this._levelDetailViewController.rectTransform, "", new Vector2(-20, -34.5f), new Vector2(20f, 6f));
  774. _starText.fontSize = 2.5f;
  775. _starText.alignment = TextAlignmentOptions.Right;
  776. _nText = BeatSaberUI.CreateText(this._levelDetailViewController.rectTransform, "", new Vector2(-20, -37.0f), new Vector2(20f, 6f));
  777. _nText.fontSize = 2.5f;
  778. _nText.alignment = TextAlignmentOptions.Right;
  779. }*/
  780. BeatmapDifficulty difficulty = this._levelDifficultyViewController.selectedDifficultyBeatmap.difficulty;
  781. string njsText;
  782. string difficultyString = difficulty.ToString();
  783. //Grab NJS for difficulty
  784. //Default to 10 if a standard level
  785. float njs = 0;
  786. if (!_model.LevelIdToCustomSongInfos.ContainsKey(level.levelID))
  787. {
  788. njsText = "OST";
  789. }
  790. else
  791. {
  792. //Grab njs from custom level
  793. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  794. foreach (var diffLevel in customLevel.customSongInfo.difficultyLevels)
  795. {
  796. if (diffLevel.difficulty == difficultyString)
  797. {
  798. GetNoteJump(diffLevel.json, out njs);
  799. }
  800. }
  801. //Show note jump speedS
  802. njsText = njs.ToString();
  803. }
  804. UIBuilder.SetStatButtonText(_njsStatButton, njsText);
  805. Logger.Debug("Checking if have info for song {0}", level.songName);
  806. if (this._model.LevelIdToScoreSaberData.ContainsKey(level.levelID))
  807. {
  808. Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  809. ScoreSaberData ppData = this._model.LevelIdToScoreSaberData[level.levelID];
  810. if (ppData.difficultyToSaberDifficulty.ContainsKey(difficultyString))
  811. {
  812. Logger.Debug("Display pp for song.");
  813. float pp = ppData.difficultyToSaberDifficulty[difficultyString].pp;
  814. float star = ppData.difficultyToSaberDifficulty[difficultyString].star;
  815. UIBuilder.SetStatButtonText(_ppStatButton, String.Format("{0:0.#}", pp));
  816. UIBuilder.SetStatButtonText(_starStatButton, String.Format("{0:0.#}", star));
  817. }
  818. else
  819. {
  820. UIBuilder.SetStatButtonText(_ppStatButton, "NA");
  821. UIBuilder.SetStatButtonText(_starStatButton, "NA");
  822. }
  823. }
  824. else
  825. {
  826. UIBuilder.SetStatButtonText(_ppStatButton, "?");
  827. UIBuilder.SetStatButtonText(_starStatButton, "?");
  828. }
  829. }
  830. }
  831. /// <summary>
  832. /// Update interactive state of the quick scroll buttons.
  833. /// </summary>
  834. private void RefreshQuickScrollButtons()
  835. {
  836. // if you are ever viewing the song list with less than 5 songs the up/down buttons do not exist.
  837. // just try and fetch them and ignore the exception.
  838. if (_tableViewPageUpButton == null)
  839. {
  840. try
  841. {
  842. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  843. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  844. }
  845. catch (Exception)
  846. {
  847. // We don't care if this fails.
  848. return;
  849. }
  850. }
  851. if (_tableViewPageDownButton == null)
  852. {
  853. try
  854. {
  855. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  856. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  857. }
  858. catch (Exception)
  859. {
  860. // We don't care if this fails.
  861. return;
  862. }
  863. }
  864. // Refresh the fast scroll buttons
  865. if (_tableViewPageUpButton != null && _pageUpFastButton != null)
  866. {
  867. _pageUpFastButton.interactable = _tableViewPageUpButton.interactable;
  868. _pageUpFastButton.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  869. }
  870. if (_tableViewPageDownButton != null && _pageUpFastButton != null)
  871. {
  872. _pageDownFastButton.interactable = _tableViewPageDownButton.interactable;
  873. _pageDownFastButton.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  874. }
  875. }
  876. /// <summary>
  877. /// Helper to quickly refresh add to favorites button
  878. /// </summary>
  879. /// <param name="levelId"></param>
  880. private void RefreshAddFavoriteButton(String levelId)
  881. {
  882. if (levelId == null)
  883. {
  884. _currentAddFavoriteButtonSprite = null;
  885. }
  886. else
  887. {
  888. if (_model.CurrentEditingPlaylistLevelIds.Contains(levelId))
  889. {
  890. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  891. }
  892. else
  893. {
  894. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  895. }
  896. }
  897. UIBuilder.SetButtonIcon(_addFavoriteButton, _currentAddFavoriteButtonSprite);
  898. }
  899. /// <summary>
  900. /// Adjust the UI colors.
  901. /// </summary>
  902. public void RefreshSortButtonUI()
  903. {
  904. // So far all we need to refresh is the sort buttons.
  905. foreach (SongSortButton sortButton in _sortButtonGroup)
  906. {
  907. //UIBuilder.SetButtonTextColor(sortButton.Button, Color.white);
  908. UIBuilder.SetButtonBorder(sortButton.Button, Color.white);
  909. if (sortButton.SortMode == _model.Settings.sortMode)
  910. {
  911. if (this._model.Settings.invertSortResults)
  912. {
  913. //UIBuilder.SetButtonTextColor(sortButton.Button, Color.red);
  914. UIBuilder.SetButtonBorder(sortButton.Button, Color.red);
  915. }
  916. else
  917. {
  918. //UIBuilder.SetButtonTextColor(sortButton.Button, Color.green);
  919. UIBuilder.SetButtonBorder(sortButton.Button, Color.green);
  920. }
  921. }
  922. }
  923. // refresh filter buttons
  924. foreach (SongFilterButton filterButton in _filterButtonGroup)
  925. {
  926. UIBuilder.SetButtonBorder(filterButton.Button, Color.white);
  927. if (filterButton.FilterMode == _model.Settings.filterMode)
  928. {
  929. UIBuilder.SetButtonBorder(filterButton.Button, Color.green);
  930. }
  931. }
  932. }
  933. /// <summary>
  934. /// Refresh the UI state of any directory buttons.
  935. /// </summary>
  936. public void RefreshDirectoryButtons()
  937. {
  938. // bail if no button, likely folder support not enabled.
  939. if (_upFolderButton == null)
  940. {
  941. return;
  942. }
  943. if (_model.DirStackSize > 1)
  944. {
  945. _upFolderButton.interactable = true;
  946. }
  947. else
  948. {
  949. _upFolderButton.interactable = false;
  950. }
  951. }
  952. /// <summary>
  953. /// Try to refresh the song list. Broken for now.
  954. /// </summary>
  955. public void RefreshSongList()
  956. {
  957. Logger.Info("Refreshing the song list view.");
  958. try
  959. {
  960. if (_model.SortedSongList == null)
  961. {
  962. Logger.Debug("Songs are not sorted yet, nothing to refresh.");
  963. return;
  964. }
  965. LevelSO[] levels = _model.SortedSongList.ToArray();
  966. //_levelListViewController.SetLevels(levels);
  967. LevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<LevelListViewController>("_levelListViewController");
  968. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  969. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  970. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  971. if (tableView == null)
  972. {
  973. Logger.Debug("TableView is not available yet, cannot refresh...");
  974. return;
  975. }
  976. tableView.ReloadData();
  977. String selectedLevelID = null;
  978. if (_model.LastSelectedLevelId != null)
  979. {
  980. selectedLevelID = _model.LastSelectedLevelId;
  981. Logger.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  982. }
  983. else
  984. {
  985. if (levels.Length > 0)
  986. {
  987. selectedLevelID = levels.FirstOrDefault().levelID;
  988. }
  989. }
  990. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  991. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  992. {
  993. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  994. }
  995. RefreshSortButtonUI();
  996. RefreshQuickScrollButtons();
  997. }
  998. catch (Exception e)
  999. {
  1000. Logger.Exception("Exception refreshing song list:", e);
  1001. }
  1002. }
  1003. /// <summary>
  1004. /// Scroll TableView to proper row, fire events.
  1005. /// </summary>
  1006. /// <param name="table"></param>
  1007. /// <param name="levelID"></param>
  1008. private void SelectAndScrollToLevel(LevelListTableView table, string levelID)
  1009. {
  1010. // Check once per load
  1011. if (!_checkedForTwitchPlugin)
  1012. {
  1013. Logger.Info("Checking for BeatSaber Twitch Integration Plugin...");
  1014. // Try to detect BeatSaber Twitch Integration Plugin
  1015. _detectedTwitchPluginQueue = Resources.FindObjectsOfTypeAll<VRUIViewController>().Any(x => x.name == "RequestInfo");
  1016. Logger.Info("BeatSaber Twitch Integration plugin detected: " + _detectedTwitchPluginQueue);
  1017. _checkedForTwitchPlugin = true;
  1018. }
  1019. // Skip scrolling to level if twitch plugin has queue active.
  1020. if (_detectedTwitchPluginQueue)
  1021. {
  1022. Logger.Debug("Skipping SelectAndScrollToLevel() because we detected Twitch Integrtion Plugin has a Queue active...");
  1023. return;
  1024. }
  1025. int row = table.RowNumberForLevelID(levelID);
  1026. TableView tableView = table.GetComponentInChildren<TableView>();
  1027. tableView.SelectRow(row, true);
  1028. tableView.ScrollToRow(row, true);
  1029. _lastRow = row;
  1030. }
  1031. /// <summary>
  1032. /// Helper for updating the model (which updates the song list)c
  1033. /// </summary>
  1034. public void UpdateSongList()
  1035. {
  1036. Logger.Trace("UpdateSongList()");
  1037. // UI not created yet.
  1038. BeatmapCharacteristicSO bc = null;
  1039. if (_beatmapCharacteristicSelectionViewController != null)
  1040. {
  1041. bc = _beatmapCharacteristicSelectionViewController.selectedBeatmapCharacteristic;
  1042. }
  1043. _model.UpdateSongLists(bc);
  1044. this.RefreshDirectoryButtons();
  1045. }
  1046. //Pull njs from a difficulty, based on private function from SongLoader
  1047. public void GetNoteJump(string json, out float noteJumpSpeed)
  1048. {
  1049. noteJumpSpeed = 0;
  1050. var split = json.Split(':');
  1051. for (var i = 0; i < split.Length; i++)
  1052. {
  1053. if (split[i].Contains("_noteJumpSpeed"))
  1054. {
  1055. noteJumpSpeed = Convert.ToSingle(split[i + 1].Split(',')[0], CultureInfo.InvariantCulture);
  1056. }
  1057. }
  1058. }
  1059. #if DEBUG
  1060. /// <summary>
  1061. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  1062. /// </summary>
  1063. public void LateUpdate()
  1064. {
  1065. CheckDebugUserInput();
  1066. }
  1067. /// <summary>
  1068. /// Map some key presses directly to UI interactions to make testing easier.
  1069. /// </summary>
  1070. private void CheckDebugUserInput()
  1071. {
  1072. try
  1073. {
  1074. if (this._levelListViewController != null && this._levelListViewController.isActiveAndEnabled)
  1075. {
  1076. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  1077. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.X))
  1078. {
  1079. this._beatmapCharacteristicSelectionViewController.HandleBeatmapCharacteristicSegmentedControlDidSelectCell(null, 1);
  1080. }
  1081. else if (Input.GetKeyDown(KeyCode.X))
  1082. {
  1083. this._beatmapCharacteristicSelectionViewController.HandleBeatmapCharacteristicSegmentedControlDidSelectCell(null, 0);
  1084. }
  1085. // back
  1086. if (Input.GetKeyDown(KeyCode.Escape))
  1087. {
  1088. this._levelSelectionNavigationController.DismissButtonWasPressed();
  1089. }
  1090. // select current sort mode again (toggle inverting)
  1091. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.BackQuote))
  1092. {
  1093. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1094. }
  1095. // cycle sort modes
  1096. else if (Input.GetKeyDown(KeyCode.BackQuote))
  1097. {
  1098. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  1099. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1100. }
  1101. // filter favorites
  1102. if (Input.GetKeyDown(KeyCode.F1))
  1103. {
  1104. OnFavoriteFilterButtonClickEvent();
  1105. }
  1106. // filter playlists
  1107. if (Input.GetKeyDown(KeyCode.F2))
  1108. {
  1109. OnPlaylistButtonClickEvent();
  1110. }
  1111. // filter search
  1112. if (Input.GetKeyDown(KeyCode.F3))
  1113. {
  1114. OnSearchButtonClickEvent();
  1115. }
  1116. // delete
  1117. if (Input.GetKeyDown(KeyCode.Delete))
  1118. {
  1119. _deleteButton.onClick.Invoke();
  1120. }
  1121. // c - select difficulty for top song
  1122. if (Input.GetKeyDown(KeyCode.C))
  1123. {
  1124. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  1125. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  1126. }
  1127. // v start a song or enter a folder
  1128. if (Input.GetKeyDown(KeyCode.Return))
  1129. {
  1130. if (_playButton.isActiveAndEnabled)
  1131. {
  1132. _playButton.onClick.Invoke();
  1133. }
  1134. else if (_enterFolderButton.isActiveAndEnabled)
  1135. {
  1136. _enterFolderButton.onClick.Invoke();
  1137. }
  1138. }
  1139. // backspace - up a folder
  1140. if (Input.GetKeyDown(KeyCode.Backspace))
  1141. {
  1142. _upFolderButton.onClick.Invoke();
  1143. }
  1144. // change song index
  1145. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  1146. {
  1147. _pageUpFastButton.onClick.Invoke();
  1148. }
  1149. else if (Input.GetKeyDown(KeyCode.N))
  1150. {
  1151. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  1152. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  1153. }
  1154. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  1155. {
  1156. _pageDownFastButton.onClick.Invoke();
  1157. }
  1158. else if (Input.GetKeyDown(KeyCode.M))
  1159. {
  1160. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  1161. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  1162. }
  1163. // add to favorites
  1164. if (Input.GetKeyDown(KeyCode.KeypadPlus))
  1165. {
  1166. ToggleSongInPlaylist();
  1167. }
  1168. }
  1169. else if (_deleteDialog != null && _deleteDialog.isInViewControllerHierarchy)
  1170. {
  1171. // accept delete
  1172. if (Input.GetKeyDown(KeyCode.Return))
  1173. {
  1174. _deleteDialog.GetPrivateField<Button>("_okButton").onClick.Invoke();
  1175. }
  1176. if (Input.GetKeyDown(KeyCode.Escape))
  1177. {
  1178. _deleteDialog.GetPrivateField<Button>("_cancelButton").onClick.Invoke();
  1179. }
  1180. }
  1181. else
  1182. {
  1183. if (Input.GetKeyDown(KeyCode.B))
  1184. {
  1185. Logger.Debug("Invoking OK Button");
  1186. VRUIViewController view = Resources.FindObjectsOfTypeAll<VRUIViewController>().First(x => x.name == "StandardLevelResultsViewController");
  1187. view.GetComponentsInChildren<Button>().First(x => x.name == "Ok").onClick.Invoke();
  1188. }
  1189. }
  1190. }
  1191. catch (Exception e)
  1192. {
  1193. Logger.Exception("Debug Input caused Exception: ", e);
  1194. }
  1195. }
  1196. #endif
  1197. }
  1198. }