diff options
Diffstat (limited to 'Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs')
-rw-r--r-- | Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs new file mode 100644 index 00000000..916f1859 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs @@ -0,0 +1,452 @@ +#region + +using System; +using System.ComponentModel; +using System.Linq; +using Android.App; +using Android.Content; +using Android.Content.Res; +using Android.OS; +using Android.Runtime; +using Android.Support.V7.App; +using Android.Util; +using Android.Views; +using Android.Widget; +using Xamarin.Forms.Platform.Android.AppCompat; +using AToolbar = Android.Support.V7.Widget.Toolbar; +using AColor = Android.Graphics.Color; +using AlertDialog = Android.Support.V7.App.AlertDialog; +using ARelativeLayout = Android.Widget.RelativeLayout; + +#endregion + +namespace Xamarin.Forms.Platform.Android +{ + public class FormsAppCompatActivity : AppCompatActivity, IDeviceInfoProvider, IStartActivityForResult + { + public delegate bool BackButtonPressedEventHandler(object sender, EventArgs e); + + readonly ConcurrentDictionary<int, Action<Result, Intent>> _activityResultCallbacks = new ConcurrentDictionary<int, Action<Result, Intent>>(); + + Application _application; + int _busyCount; + AndroidApplicationLifecycleState _currentState; + ARelativeLayout _layout; + + int _nextActivityResultCallbackKey; + + AppCompat.Platform _platform; + + AndroidApplicationLifecycleState _previousState; + + bool _renderersAdded; + int _statusBarHeight = -1; + global::Android.Views.View _statusBarUnderlay; + + protected FormsAppCompatActivity() + { + _previousState = AndroidApplicationLifecycleState.Uninitialized; + _currentState = AndroidApplicationLifecycleState.Uninitialized; + } + + public event EventHandler ConfigurationChanged; + + int IStartActivityForResult.RegisterActivityResultCallback(Action<Result, Intent> callback) + { + int requestCode = _nextActivityResultCallbackKey; + + while (!_activityResultCallbacks.TryAdd(requestCode, callback)) + { + _nextActivityResultCallbackKey += 1; + requestCode = _nextActivityResultCallbackKey; + } + + _nextActivityResultCallbackKey += 1; + + return requestCode; + } + + void IStartActivityForResult.UnregisterActivityResultCallback(int requestCode) + { + Action<Result, Intent> callback; + _activityResultCallbacks.TryRemove(requestCode, out callback); + } + + public override void OnBackPressed() + { + if (BackPressed != null && BackPressed(this, EventArgs.Empty)) + return; + base.OnBackPressed(); + } + + public override void OnConfigurationChanged(Configuration newConfig) + { + base.OnConfigurationChanged(newConfig); + ConfigurationChanged?.Invoke(this, new EventArgs()); + } + + public override bool OnOptionsItemSelected(IMenuItem item) + { + if (item.ItemId == global::Android.Resource.Id.Home) + BackPressed?.Invoke(this, EventArgs.Empty); + + return base.OnOptionsItemSelected(item); + } + + public void SetStatusBarColor(AColor color) + { + _statusBarUnderlay.SetBackgroundColor(color); + } + + protected void LoadApplication(Application application) + { + if (!_renderersAdded) + { + RegisterHandlerForDefaultRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer), typeof(NavigationRenderer)); + RegisterHandlerForDefaultRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer), typeof(TabbedRenderer)); + RegisterHandlerForDefaultRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer), typeof(MasterDetailRenderer)); + RegisterHandlerForDefaultRenderer(typeof(Button), typeof(AppCompat.ButtonRenderer), typeof(ButtonRenderer)); + RegisterHandlerForDefaultRenderer(typeof(Switch), typeof(AppCompat.SwitchRenderer), typeof(SwitchRenderer)); + RegisterHandlerForDefaultRenderer(typeof(Picker), typeof(AppCompat.PickerRenderer), typeof(PickerRenderer)); + RegisterHandlerForDefaultRenderer(typeof(Frame), typeof(AppCompat.FrameRenderer), typeof(FrameRenderer)); + RegisterHandlerForDefaultRenderer(typeof(CarouselPage), typeof(AppCompat.CarouselPageRenderer), typeof(CarouselPageRenderer)); + } + + if (application == null) + throw new ArgumentNullException("application"); + + _application = application; + Xamarin.Forms.Application.Current = application; + + application.PropertyChanged += AppOnPropertyChanged; + + SetMainPage(); + } + + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + base.OnActivityResult(requestCode, resultCode, data); + + Action<Result, Intent> callback; + + if (_activityResultCallbacks.TryGetValue(requestCode, out callback)) + callback(resultCode, data); + } + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + AToolbar bar; + if (ToolbarResource != 0) + { + bar = LayoutInflater.Inflate(ToolbarResource, null).JavaCast<AToolbar>(); + if (bar == null) + throw new InvalidOperationException("ToolbarResource must be set to a Android.Support.V7.Widget.Toolbar"); + } + else + bar = new AToolbar(this); + + SetSupportActionBar(bar); + + Window.SetSoftInputMode(SoftInput.AdjustPan); + + _layout = new ARelativeLayout(BaseContext); + SetContentView(_layout); + + Xamarin.Forms.Application.ClearCurrent(); + + _previousState = _currentState; + _currentState = AndroidApplicationLifecycleState.OnCreate; + + OnStateChanged(); + + _statusBarUnderlay = new global::Android.Views.View(this); + var layoutParameters = new ARelativeLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, GetStatusBarHeight()) { AlignWithParent = true }; + layoutParameters.AddRule(LayoutRules.AlignTop); + _statusBarUnderlay.LayoutParameters = layoutParameters; + _layout.AddView(_statusBarUnderlay); + + if (Forms.IsLollipopOrNewer) + { + Window.DecorView.SystemUiVisibility = (StatusBarVisibility)(SystemUiFlags.LayoutFullscreen | SystemUiFlags.LayoutStable); + Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds); + Window.SetStatusBarColor(AColor.Transparent); + + int primaryColorDark = GetColorPrimaryDark(); + + if (primaryColorDark != 0) + { + int r = AColor.GetRedComponent(primaryColorDark); + int g = AColor.GetGreenComponent(primaryColorDark); + int b = AColor.GetBlueComponent(primaryColorDark); + int a = AColor.GetAlphaComponent(primaryColorDark); + SetStatusBarColor(AColor.Argb(a, r, g, b)); + } + } + } + + protected override void OnDestroy() + { + // may never be called + base.OnDestroy(); + + MessagingCenter.Unsubscribe<Page, AlertArguments>(this, Page.AlertSignalName); + MessagingCenter.Unsubscribe<Page, bool>(this, Page.BusySetSignalName); + MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName); + + if (_platform != null) + _platform.Dispose(); + } + + protected override void OnNewIntent(Intent intent) + { + base.OnNewIntent(intent); + } + + protected override void OnPause() + { + _layout.HideKeyboard(true); + + // Stop animations or other ongoing actions that could consume CPU + // Commit unsaved changes, build only if users expect such changes to be permanently saved when thy leave such as a draft email + // Release system resources, such as broadcast receivers, handles to sensors (like GPS), or any resources that may affect battery life when your activity is paused. + // Avoid writing to permanent storage and CPU intensive tasks + base.OnPause(); + + _previousState = _currentState; + _currentState = AndroidApplicationLifecycleState.OnPause; + + OnStateChanged(); + } + + protected override void OnRestart() + { + base.OnRestart(); + + _previousState = _currentState; + _currentState = AndroidApplicationLifecycleState.OnRestart; + + OnStateChanged(); + } + + protected override void OnResume() + { + // counterpart to OnPause + base.OnResume(); + + _previousState = _currentState; + _currentState = AndroidApplicationLifecycleState.OnResume; + + OnStateChanged(); + } + + protected override void OnStart() + { + base.OnStart(); + + _previousState = _currentState; + _currentState = AndroidApplicationLifecycleState.OnStart; + + OnStateChanged(); + } + + // Scenarios that stop and restart your app + // -- Switches from your app to another app, activity restarts when clicking on the app again. + // -- Action in your app that starts a new Activity, the current activity is stopped and the second is created, pressing back restarts the activity + // -- The user receives a phone call while using your app on his or her phone + protected override void OnStop() + { + // writing to storage happens here! + // full UI obstruction + // users focus in another activity + // perform heavy load shutdown operations + // clean up resources + // clean up everything that may leak memory + base.OnStop(); + + _previousState = _currentState; + _currentState = AndroidApplicationLifecycleState.OnStop; + + OnStateChanged(); + } + + internal int GetStatusBarHeight() + { + if (_statusBarHeight >= 0) + return _statusBarHeight; + + var result = 0; + int resourceId = Resources.GetIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) + result = Resources.GetDimensionPixelSize(resourceId); + return _statusBarHeight = result; + } + + void AppOnPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == "MainPage") + InternalSetPage(_application.MainPage); + } + + int GetColorPrimaryDark() + { + FormsAppCompatActivity context = this; + int id = global::Android.Resource.Attribute.ColorPrimaryDark; + using(var value = new TypedValue()) + { + try + { + Resources.Theme theme = context.Theme; + if (theme != null && theme.ResolveAttribute(id, value, true)) + { + if (value.Type >= DataType.FirstInt && value.Type <= DataType.LastInt) + return value.Data; + if (value.Type == DataType.String) + return context.Resources.GetColor(value.ResourceId); + } + } + catch (Exception ex) + { + } + + return -1; + } + } + + void InternalSetPage(Page page) + { + if (!Forms.IsInitialized) + throw new InvalidOperationException("Call Forms.Init (Activity, Bundle) before this"); + + if (_platform != null) + { + _platform.SetPage(page); + return; + } + + _busyCount = 0; + MessagingCenter.Subscribe<Page, bool>(this, Page.BusySetSignalName, OnPageBusy); + MessagingCenter.Subscribe<Page, AlertArguments>(this, Page.AlertSignalName, OnAlertRequested); + MessagingCenter.Subscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName, OnActionSheetRequested); + + _platform = new AppCompat.Platform(this); + if (_application != null) + _application.Platform = _platform; + _platform.SetPage(page); + _layout.AddView(_platform); + _layout.BringToFront(); + } + + void OnActionSheetRequested(Page sender, ActionSheetArguments arguments) + { + var builder = new AlertDialog.Builder(this); + builder.SetTitle(arguments.Title); + string[] items = arguments.Buttons.ToArray(); + builder.SetItems(items, (o, args) => arguments.Result.TrySetResult(items[args.Which])); + + if (arguments.Cancel != null) + builder.SetPositiveButton(arguments.Cancel, (o, args) => arguments.Result.TrySetResult(arguments.Cancel)); + + if (arguments.Destruction != null) + builder.SetNegativeButton(arguments.Destruction, (o, args) => arguments.Result.TrySetResult(arguments.Destruction)); + + AlertDialog dialog = builder.Create(); + builder.Dispose(); + //to match current functionality of renderer we set cancelable on outside + //and return null + dialog.SetCanceledOnTouchOutside(true); + dialog.CancelEvent += (o, e) => arguments.SetResult(null); + dialog.Show(); + } + + void OnAlertRequested(Page sender, AlertArguments arguments) + { + AlertDialog alert = new AlertDialog.Builder(this).Create(); + alert.SetTitle(arguments.Title); + alert.SetMessage(arguments.Message); + if (arguments.Accept != null) + alert.SetButton((int)DialogButtonType.Positive, arguments.Accept, (o, args) => arguments.SetResult(true)); + alert.SetButton((int)DialogButtonType.Negative, arguments.Cancel, (o, args) => arguments.SetResult(false)); + alert.CancelEvent += (o, args) => { arguments.SetResult(false); }; + alert.Show(); + } + + void OnPageBusy(Page sender, bool enabled) + { + _busyCount = Math.Max(0, enabled ? _busyCount + 1 : _busyCount - 1); + + if (!Forms.SupportsProgress) + return; + + SetProgressBarIndeterminate(true); + UpdateProgressBarVisibility(_busyCount > 0); + } + + async void OnStateChanged() + { + if (_application == null) + return; + + if (_previousState == AndroidApplicationLifecycleState.OnCreate && _currentState == AndroidApplicationLifecycleState.OnStart) + _application.SendStart(); + else if (_previousState == AndroidApplicationLifecycleState.OnStop && _currentState == AndroidApplicationLifecycleState.OnRestart) + _application.SendResume(); + else if (_previousState == AndroidApplicationLifecycleState.OnPause && _currentState == AndroidApplicationLifecycleState.OnStop) + await _application.SendSleepAsync(); + } + + void RegisterHandlerForDefaultRenderer(Type target, Type handler, Type filter) + { + Type current = Registrar.Registered.GetHandlerType(filter); + if (current == target) + return; + + Registrar.Registered.Register(target, handler); + } + + void SetMainPage() + { + InternalSetPage(_application.MainPage); + } + + void UpdateProgressBarVisibility(bool isBusy) + { + if (!Forms.SupportsProgress) + return; + + SetProgressBarIndeterminateVisibility(isBusy); + } + + internal class DefaultApplication : Application + { + } + + #region Statics + + public static event BackButtonPressedEventHandler BackPressed; + + public static int TabLayoutResource { get; set; } + + public static int ToolbarResource { get; set; } + + internal static int GetUniqueId() + { + // getting unique Id's is an art, and I consider myself the Jackson Pollock of the field + if ((int)Build.VERSION.SdkInt >= 17) + return global::Android.Views.View.GenerateViewId(); + + // Numbers higher than this range reserved for xml + // If we roll over, it can be exceptionally problematic for the user if they are still retaining things, android's internal implementation is + // basically identical to this except they do a lot of locking we don't have to because we know we only do this + // from the UI thread + if (s_id >= 0x00ffffff) + s_id = 0x00000400; + return s_id++; + } + + static int s_id = 0x00000400; + + #endregion + } +}
\ No newline at end of file |