---
# System prepended metadata

title: Untitled

---

//-----------------------------------------------------------------------
// <copyright file="Title.cs" company="infiniteloop Co., Ltd">
//     Copyright (c) infiniteloop Co., Ltd All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Linq;
using Infiniteloop.SystemUtility.Extensions;
using Infiniteloop.UnityUtility.Extensions;
using Infiniteloop.VRLive.ActionLog;
using Infiniteloop.VRLive.ActionLog.Types;
using Infiniteloop.VRLive.Message;
using TMPro;
using UniRx;
using UniRx.Async;
using UniRx.Async.Triggers;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using VirtualCast.Localize;
using VirtualCast.StudioResolver;
using VirtualCast.Term;
using VirtualCast.Title;
using VirtualCast.UserConfiguration;
using VirtualCast.Utilities;
using VirtualCast.VrInteraction.Core;
using VirtualCast.VrInteraction.SteamVr;
using Zenject;

namespace Infiniteloop.VRLive
{
    /// <summary>
    /// タイトルの中央実装。全体の動きを管理したりデータ保持などをしている。
    /// </summary>
    public sealed class Title : MonoBehaviour
    {
        private UserSaveData UserSaveData => _userSaveData;

        [Inject]
        private readonly UserSaveData _userSaveData;

        [Inject]
        private readonly TitleModel _model;

        [Inject]
        private readonly TitleVRMConfirmation _titleVrmConfirmation;

        [Inject]
        private readonly ActionLogger _actionLogger;

        [Inject]
        private readonly UserConfigurationProvider _configurationProvider;

        [Inject]
        private readonly IMiscConfig _miscConfig;

        [Inject]
        private readonly TransitionManager _transitionManager;

        [Inject]
        private readonly TitleVRMLoader _titleVrmLoader;

        [Inject]
        private readonly TitleLoading _titleLoading;

        [Inject]
        private readonly TitleDialog _titleDialog;

        [Inject]
        private readonly IXrDeviceInitializer _xrDeviceInitializer;

        [Inject]
        ZenjectSceneLoader _zenjectSceneLoader;

        [Inject]
        private readonly IStudioResolver _studioResolver;

        /// <summary>
        /// タブバーと、表示のルートGameObjectを紐付けるクラス
        /// </summary>
        [Serializable]
        private class TabBarData
        {
            /// <summary>スタジオの種類</summary>
            public TitleModel.Types.BroadcastMethod BroadcastingMethod => this.broadcastingMethod;

            /// <summary>このタブに遷移する事が出来るボタン</summary>
            /// <remarks>各タブごとにボタンがあるので、複数存在しうる</remarks>
            public Button[] Buttons => this.buttons;

            /// <summary>ルートとなるGameObject</summary>
            public GameObject Root => this.root;

            [SerializeField]
            private TitleModel.Types.BroadcastMethod broadcastingMethod;

            [SerializeField]
            private Button[] buttons;

            [SerializeField]
            private GameObject root;
        }

        /// <summary>
        /// タブバーとルートのGameObjectの紐付けデータ
        /// </summary>
        [SerializeField]
        private TabBarData[] tabs;

        /// <summary>
        /// ダイレクトビューモード専用タブ
        /// </summary>
        [SerializeField]
        private GameObject directViewModeTabRoot;

        /// <summary>
        /// バージョン表記部分
        /// </summary>
        [SerializeField]
        private TextMeshProUGUI versionText;

        /// <summary>
        /// オプションボタン
        /// </summary>
        [SerializeField]
        private Button optionsButton;

        /// <summary>
        /// ユーザーID表示部分
        /// </summary>
        [SerializeField]
        private TextMeshProUGUI userIdText;

        /// <summary>
        /// config.jsonロード成功表記
        /// </summary>
        [SerializeField]
        private TextMeshProUGUI succeededLoadingConfigJsonText;

        /// <summary>
        /// ライセンス表示ボタン
        /// </summary>
        [SerializeField]
        private Button licenseButton;

        /// <summary>
        /// 利用規約言語管理アセット
        /// </summary>
        [SerializeField]
        private TermLanguageAsset termLanguageAsset;

        /// <summary>
        /// (Unity)初期化処理
        /// </summary>
        private void Awake()
        {
            // 解像度設定
            Resolution.SetTitle();
            // 既存の配信・セッションがあれば停止する
            Session.Clear();

            // 初回起動時、言語がまだ設定されていなければ、言語設定ウィンドウを表示する
            if (UserSaveData.ClientLanguage == ClientLanguage.None)
            {
                SceneManager.LoadScene(SceneName.TitleSelectLanguageOverlay, LoadSceneMode.Additive);
                SceneManager.sceneUnloaded += DidUnloadScene;
            }

            // バージョン表記
            this.versionText.text = AppVersion.GetText(_miscConfig.ConnectingServer);

            // デバッグ環境ではUserId/ClientIdを表示、リリース時はUserIDのみ表示
#if DEBUG
            this.userIdText.text = $"UserId: {UserSaveData.UserId} / ClientId: {UserSaveData.ClientId}";
#else
            this.userIdText.text = $"UserId: {UserSaveData.UserId}";
#endif

            this.succeededLoadingConfigJsonText.gameObject.SetActive(_configurationProvider.ConfigJSON != null);

#if !(DEBUG || UNITY_EDITOR || DEVELOPMENT_BUILD)
            Cursor.visible = true;
#endif
        }

        private async void Start()
        {
            await TitleCheck();

            StartTitle();
        }

        private void OnEnable()
        {
            if (_miscConfig.ApplicationMode == ApplicationMode.Default ||
                _miscConfig.ApplicationMode == ApplicationMode.MotionCaptureWithHmd)
            {
                _xrDeviceInitializer.InitializeXrDeviceAsync(this.GetCancellationTokenOnDestroy()).Forget();
            }
        }

        private bool HasAlreadyAgreed()
        {
            // 必ずある日本語でMD5を検証する
            // 他言語の利用規約は日本語規約が変わると一緒に変わる前提
            var text = termLanguageAsset.GetTerm(ClientLanguage.Ja);

            var md5Bytes = Converter.ComputeMd5(text);
            return UserSaveData.TermMd5.SequenceEqual(md5Bytes);
        }

        private async UniTask TitleCheck()
        {
            var ct = this.CancelOnDestroy();

#if DEBUG || UNITY_EDITOR || SELECTABLE_ENDPOINT
            var connectingServerCheck = new ConnectingServerCheck(_miscConfig, _titleDialog);
            // バージョンチェック前に接続サーバーの確認が必須

            await connectingServerCheck.CheckAsync(ct);
#endif

            var versionCheck = GetComponent<VersionCheck>();
            await versionCheck.CheckAsync(ct);
        }

        private void DidUnloadScene(Scene unloadScene)
        {
            if (unloadScene.name == SceneName.TitleSelectLanguageOverlay)
            {
                // 利用規約画面
                ShowTitleAcceptTermsOverlay();
            }
        }

        /// <summary>
        /// 開始処理
        /// </summary>
        /// <remarks>
        /// この関数を呼ばれてからタイトルの処理が活性化する。
        /// </remarks>
        private void StartTitle()
        {
            switch (_miscConfig.ApplicationMode)
            {
                case ApplicationMode.DirectViewer:
                    ShowDirectViewTab();
                    break;

                default:
                    ShowDefaultTabs();
                    break;
            }

            // オプションボタンの購読
            this.optionsButton
                .OnClickAsObservable()
                .Subscribe(_ =>
                {
                    SceneManager.LoadScene(SceneName.TitleConfigOverlay, LoadSceneMode.Additive);
                });

            // ライセンスダイアログ表示ボタン
            this.licenseButton
                .OnClickAsObservable()
                .Subscribe(_ =>
                {
                    SceneManager.LoadScene(SceneName.TitleLicenseOverlay, LoadSceneMode.Additive);
                });

            // 利用規約画面
            ShowTitleAcceptTermsOverlay();
        }

        /// <summary>
        /// 利用規約画面の表示処理
        /// </summary>
        private void ShowTitleAcceptTermsOverlay()
        {
            if (!HasAlreadyAgreed() && UserSaveData.ClientLanguage != ClientLanguage.None)
            {
                SceneManager.LoadScene(SceneName.TitleAcceptTermsOverlay, LoadSceneMode.Additive);
            }
        }

        /// <summary>
        /// 通常のタブ表示処理
        /// </summary>
        private void ShowDefaultTabs()
        {
            // タブボタンが押されたら選択中のスタジオ種類を更新する
            foreach (var tab in this.tabs)
            {
                foreach (var button in tab.Buttons)
                {
                    button
                        .OnClickAsObservable()
                        .Subscribe(_ => SelectTab(tab))
                        .AddTo(gameObject);
                }
            }

            // 選択中のスタジオ種類設定時にUIに反映
            this._model
                .ObserveEveryValueChanged(m => m.CurrentBroadcastingMethod)
                .Subscribe(current =>
                {
                    foreach (var tab in this.tabs)
                    {
                        var selected = tab.BroadcastingMethod == current;
                        tab.Root.SetActive(selected);
                    }
                })
                .AddTo(gameObject);
        }

        /// <summary>
        /// ダイレクトビューモード用のタブを表示する
        /// </summary>
        public void ShowDirectViewTab()
        {
            foreach (var tab in this.tabs)
            {
                tab.Root.SetActive(false);
            }

            this.directViewModeTabRoot.SetActive(true);
        }

        /// <summary>
        /// スタジオに入室(遷移)する
        /// </summary>
        /// <remarks>
        /// TitleTabの実装から呼ばれる
        /// <seealso cref="TitleTabBase" />
        /// </remarks>
        public void EnterStudio()
        {
            Logger.Info("Title", "EnterStudio");

            AnalyticsSender.SendUserConfig(_configurationProvider);
            AnalyticsSender.SendUserEnvironmentInfo();

            this._model.Save();
            UserSaveData.Save();

            _actionLogger.WriteAsync(new Boot()).FireAndForget();

            var loadingLock = _titleLoading.Lock();

            _titleVrmLoader.LoadVrmFromConfig()
                .ToObservable(Scheduler.MainThread)
                .SelectMany(loadedVrm => _titleVrmConfirmation.RequestVrmAgreements(loadedVrm).ToObservable(Scheduler.MainThread))
                .Finally(loadingLock.Dispose)
                .Subscribe(async _ =>
                {
                    // 最初はマイスタジオに入る
                    _transitionManager.Clear();
                    _transitionManager.ReserveToGoTo(Session.Broadcast?.OwningStudioAddress ?? StudioAddress.Local);

                    // 入力された凸先を記憶しておく
                    var address = string.IsNullOrEmpty(_model.TotsuStudioId) ? null : StudioAddress.Parse(_model.TotsuStudioId);

                    // 一時的にプライベートスタジオと配信スタジオの区別はTitleをランダムな文字列のハッシュ値で固定してそれが一致するかどうがで判別する
                    if (address != null && address.Kind == StudioKind.Broadcast)
                    {
                        var desc = await _studioResolver.FetchDescriptionAsync(address);
                        var info = await _studioResolver.FetchInstanceInfoAsync(address);
                        var nickName = info.RoomName.Replace(Localized.Text("StudioScene/BroadcastStudioName", ""), "");
                        var md5 = Converter.ComputeMd5(nickName);
                        var md5Hex = string.Concat(md5.Select(b => b.ToString("x2")));
                        if (desc.Title == md5Hex)
                            address.Kind = StudioKind.Private;
                    }
                    _transitionManager.SpecifiedStudioAddress = address;

                    if (_miscConfig.ApplicationMode == ApplicationMode.MotionCapture ||
                        _miscConfig.ApplicationMode == ApplicationMode.MotionCaptureWithHmd)
                    {
                        _zenjectSceneLoader.LoadScene(SceneName.StudioScene, LoadSceneMode.Single, container =>
                        {
                            container.Bind<StudioAddress>().FromInstance(_transitionManager.TransitionTargetAddress).AsSingle();
                        });
                    }
                    else
                    {
                        SceneManager.LoadScene(SceneName.CalibrationScene);
                    }
                });
        }

        /// <summary>
        /// タブ選択処理
        /// </summary>
        /// <param name="selected">選択されたタブ</param>
        private void SelectTab(TabBarData selected)
        {
            Logger.Verbose("Title", $"Broadcasting method = {selected.BroadcastingMethod}");
            this._model.CurrentBroadcastingMethod = selected.BroadcastingMethod;
        }

        private void OnDestroy()
        {
            SceneManager.sceneUnloaded -= DidUnloadScene;
        }
    }
}
