-
Notifications
You must be signed in to change notification settings - Fork 0
/
MainActivity.cs
371 lines (309 loc) · 19.9 KB
/
MainActivity.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
using Android.App;
using Android.OS;
using Android.Views;
using AndroidX.AppCompat.Widget;
using AndroidX.Core.View;
using AndroidX.DrawerLayout.Widget;
using AndroidX.Navigation;
using AndroidX.Navigation.Fragment;
using AndroidX.Navigation.UI;
using AndroidX.Preference;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Navigation;
namespace com.companyname.SplashScreen2
{
[Activity(Label = "@string/app_name", MainLauncher = true)]
public class MainActivity : BaseActivity, IOnApplyWindowInsetsListener,
NavController.IOnDestinationChangedListener,
NavigationBarView.IOnItemSelectedListener,
NavigationView.IOnNavigationItemSelectedListener
{
internal readonly string logTag = "GLM - MainActivity";
private AppBarConfiguration appBarConfiguration;
private NavigationView navigationView;
private DrawerLayout drawerLayout;
private BottomNavigationView bottomNavigationView;
private NavController navController;
private Toolbar toolbar;
// Preferences variables - see OnDestinationChanged where it is checked
private bool devicesWithNotchesAllowFullScreen; // allow full screen for devices with notches
private bool animateFragments; // animate fragments
#region OnCreate
protected override void OnCreate(Bundle savedInstanceState)
{
AndroidX.Core.SplashScreen.SplashScreen.InstallSplashScreen(this);
base.OnCreate(savedInstanceState);
// Only for demonstration purposes in that you can easily see the background color and the launch icon. Remove for production build.
System.Threading.Thread.Sleep(500);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
// This rather than android:windowTranslucentStatus in styles seems to have fixed the problem with the OK button on the BasicDialogFragment
// It also fixes the AppBarlayout so it extends full screen, when devicesWithNotchesAllowFullScreen = true;
Window.AddFlags(WindowManagerFlags.TranslucentStatus);
// Require a toolbar
toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
ViewCompat.SetOnApplyWindowInsetsListener(toolbar, this);
// navigationView, bottomNavigationView for NavigationUI and drawerLayout for the AppBarConfiguration and NavigationUI
navigationView = FindViewById<NavigationView>(Resource.Id.nav_view);
drawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
bottomNavigationView = FindViewById<BottomNavigationView>(Resource.Id.bottom_nav);
// NavHostFragment so we can get a NavController
NavHostFragment navHostFragment = SupportFragmentManager.FindFragmentById(Resource.Id.nav_host) as NavHostFragment;
navController = navHostFragment.NavController;
// These are the fragments that you don't wont the back button of the toolbar to display on e.g. topLevel fragments. They correspond to the items of the NavigationView.
int[] topLevelDestinationIds = new int[] { Resource.Id.home_fragment, Resource.Id.gallery_fragment, Resource.Id.slideshow_fragment };
appBarConfiguration = new AppBarConfiguration.Builder(topLevelDestinationIds).SetOpenableLayout(drawerLayout).Build(); // SetDrawerLayout replaced with SetOpenableLayout
NavigationUI.SetupActionBarWithNavController(this, navController, appBarConfiguration);
// Notes using both Navigation.Fragment and Navigation.UI version 2.3.5.3. Navigation.UI therefore includes Android.Material 1.4.0.4
// These two are working, but no animation, other than when the two fragments opened from slideshowFragment close, made possible because of HandleBackPressed(NavOptions navOptions)
// Could fix by adding animation to the graph, but that limits the app to only one type of animation. Therefore replacing with SetNavigationItemSelectedListener
// That solves the problem of animating the top level fragments, but opening both fragments via the BottomNavigationView still have no animation.
// So will replace NavigationUI.SetupWithNavController(bottomNavigationView, navController) with BottomNavigationView_ItemSelected
//NavigationUI.SetupWithNavController(navigationView, navController);
//NavigationUI.SetupWithNavController(bottomNavigationView, navController);
// Upgrading to Navigation.Fragment and Navigation.UI version 2.4.2. Navigation.UI includes now Android.Material 1.5.0.2 - also tested 1.6.0
navigationView.SetNavigationItemSelectedListener(this);
bottomNavigationView.SetOnItemSelectedListener(this);
//bottomNavigationView.ItemSelected += BottomNavigationView_ItemSelected; // Alternate event handler which works for bottomNavigationView
// Add the DestinationChanged listener
navController.AddOnDestinationChangedListener(this);
// Demonstrates the problem if using 2.3.5.3 versions of Navigation and 1.4.0.4 of Material respectively
// Already using both overloads of SetupWithNavController() and there is no provision to pass a NavOptions. Since there is no animation contained in nav_graph therefore no animation when
// opening any fragment.
// The only animation is in closing the fragments as each fragment has a HandleBackPressed(NavOptions navOptions)
// Therefore to get animation we had to drop NavigationUI.SetupWithNavController(navigationView, navController) and use navigationView.SetNavigationItemSelectedListener(this) which allows creating our
// own NavOptions. At this point each fragment can be potentially animated both opening and closing.
// Test: comment out NavigationUI.SetupWithNavController(navigationView, navController) and uncomment navigationView.SetNavigationItemSelectedListener(this);
// Works as per the requirement with a choice of animations, controlled via a preference. The ugly slider animation makes checking the animation of each fragment animation easier to view.
// Upgrade Navigation packages to latest available. 2.4.2 will include material 1.5.0.2
// Clean, Rebuild Deploy. All seems ok until you try and open either of the BottomNavigationView Fragments after it has already been opened once. Will not open again without first closing
// the SlideshowFragment and then opening it again and either fragment will open, but only the one time.
// Attempts to fix.
// Comment out NavigationUI.SetupWithNavController(bottomNavigationView, navController) and uncomment bottomNavigationView.ItemSelected, which works
// It would appear that there is a problem with Xamarin.Google.Android.Material 1.5.0.2
// Next step was to add Xamarin.Google.Android.Material 1.6.0
// No change from 1.5.0.2 behavior - work arounds work as before.
}
#endregion
#region OnApplyWindowInsets
public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
{
if (v is Toolbar)
{
AndroidX.Core.Graphics.Insets statusBarInsets = insets.GetInsets(WindowInsetsCompat.Type.StatusBars());
SetMargins(v, statusBarInsets);
if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
if (insets.DisplayCutout != null)
{
if (devicesWithNotchesAllowFullScreen)
Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
else
Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.Default;
}
}
}
return insets;
}
#endregion
#region SetMargins
private void SetMargins(View v, AndroidX.Core.Graphics.Insets insets)
{
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams)v.LayoutParameters;
marginLayoutParams.LeftMargin = insets.Left;
marginLayoutParams.TopMargin = insets.Top; // top is all we are concerned with
marginLayoutParams.RightMargin = insets.Right;
marginLayoutParams.BottomMargin = insets.Bottom;
v.LayoutParameters = marginLayoutParams;
v.RequestLayout();
}
#endregion
#region OnCreateOptionsMenu
public override bool OnCreateOptionsMenu(IMenu menu)
{
base.OnCreateOptionsMenu(menu);
MenuInflater.Inflate(Resource.Menu.main, menu);
return true;
}
#endregion
#region OnSupportNavigationUp
public override bool OnSupportNavigateUp()
{
return NavigationUI.NavigateUp(navController, appBarConfiguration) || base.OnSupportNavigateUp();
}
#endregion
#region OnOptionsItemSelected
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.action_settings:
navController.Navigate(Resource.Id.settingsFragment);
break;
case Resource.Id.action_subscription_info:
ShowSubscriptionInfoDialog(GetString(Resource.String.subscription_explanation_title), GetString(Resource.String.subscription_explanation_text));
break;
}
return base.OnOptionsItemSelected(item);
}
#endregion
#region BottomNavigationView_ItemSelected
private void BottomNavigationView_ItemSelected(object sender, NavigationBarView.ItemSelectedEventArgs e)
{
if (!animateFragments)
AnimationResource.Fader2();
else
AnimationResource.Slider();
NavOptions navOptions = new NavOptions.Builder()
.SetLaunchSingleTop(true)
.SetEnterAnim(AnimationResource.EnterAnimation)
.SetExitAnim(AnimationResource.ExitAnimation)
.SetPopEnterAnim(AnimationResource.PopEnterAnimation)
.SetPopExitAnim(AnimationResource.PopExitAnimation)
.Build();
bool proceed = false;
switch (e.Item.ItemId)
{
case Resource.Id.leaderboard_fragment:
case Resource.Id.register_fragment:
proceed = true;
break;
default:
break;
}
if (proceed)
navController.Navigate(e.Item.ItemId, null, navOptions);
}
#endregion
#region NavigationBarView.IOnItemSelectedListener.OnNavigationItemSelected
//
//bool NavigationBarView.IOnItemSelectedListener.OnNavigationItemSelected(IMenuItem item)
//{
// throw new System.NotImplementedException();
//}
#endregion
#region OnNavigationItemSelected
public bool OnNavigationItemSelected(IMenuItem menuItem)
{
// Using Fader2 as the default as animateFragment is false by default - check AnimationResource.cs for different animations
if (!animateFragments)
AnimationResource.Fader2();
else
AnimationResource.Slider();
NavOptions navOptions = new NavOptions.Builder()
.SetLaunchSingleTop(true)
.SetEnterAnim(AnimationResource.EnterAnimation)
.SetExitAnim(AnimationResource.ExitAnimation)
.SetPopEnterAnim(AnimationResource.PopEnterAnimation)
.SetPopExitAnim(AnimationResource.PopExitAnimation)
.Build();
bool proceed = false;
switch (menuItem.ItemId)
{
// These are all topLevel fragments
// Add fragment classes and fragment layouts as we add to the codebase as per the NavigationView items.
// If any classes and layouts are missing, then the NavigationView will not update the item selected.
// The menuitem highlight will stay on the current item and the current fragment will remain displayed, nor will the app crash.
case Resource.Id.home_fragment:
case Resource.Id.gallery_fragment:
case Resource.Id.slideshow_fragment:
// These two are not top level fragments, these fragments are opened via the BottomNavigationView, not the NavigationView yet they still come through here.
// You need to observe how the event handler works e.g. NavigationBarView.IOnItemSelectedListener.OnNavigationItemSelected(IMenuItem item) which leads back to here.
// Note that BottomNavigationView inherits from NavigationBarView which is an abstract class.
case Resource.Id.leaderboard_fragment:
case Resource.Id.register_fragment:
proceed = true;
break;
default:
break;
}
// We have the option here of animating our toplevel destinations. If we don't want animation comment out the NavOptions.
bool handled = false;
if (proceed)
{
// I don't want to animate these two.
if (menuItem.ItemId == Resource.Id.leaderboard_fragment || menuItem.ItemId == Resource.Id.register_fragment)
navController.Navigate(menuItem.ItemId/*, null, navOptions*/); // uncomment if you want to try it or just get rid of the if else
else
navController.Navigate(menuItem.ItemId, null, navOptions);
handled = true;
}
if (drawerLayout.IsDrawerOpen(GravityCompat.Start))
drawerLayout.CloseDrawer(GravityCompat.Start);
return handled;
}
#endregion
#region OnDestinationChanged
public void OnDestinationChanged(NavController navController, NavDestination navDestination, Bundle bundle)
{
CheckForPreferenceChanges();
// The first menu item is not checked by default, so we need to check it to show it is selected on the startDestination fragment.
navigationView.Menu.FindItem(Resource.Id.home_fragment).SetChecked(navDestination.Id == Resource.Id.home_fragment);
// Decided this wasn't required as we now have it in BaseActivity
//if (navDestination.Id == Resource.Id.home_fragment)
// AppCompatDelegate.DefaultNightMode = nightModeActive ? AppCompatDelegate.ModeNightYes : AppCompatDelegate.ModeNightNo;
if (navDestination.Id == Resource.Id.slideshow_fragment)
{
bottomNavigationView.Visibility = ViewStates.Visible;
navigationView.Visibility = ViewStates.Gone;
drawerLayout.SetDrawerLockMode(DrawerLayout.LockModeLockedClosed);
}
else
{
bottomNavigationView.Visibility = ViewStates.Gone;
navigationView.Visibility = ViewStates.Visible;
drawerLayout.SetDrawerLockMode(DrawerLayout.LockModeUnlocked);
}
// By default because the LeaderboardFragment and the RegisterFragment are not top level fragments, they will default to showing a up button (left arrow) plus the title.
// If you don't want the up button, remove it here. This also means that the additional code in OnSupportNavigationUp can be removed.
if (navDestination.Id == Resource.Id.leaderboard_fragment || navDestination.Id == Resource.Id.register_fragment)
{
toolbar.Title = navDestination.Label;
toolbar.NavigationIcon = null;
}
#region Notes about Window.Attributes.LayoutInDisplayCutoutMode
// Here is a bit of a trick. If we haven't set <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> in our theme (for whatever reason). Then OnApplyWindowInsets
// will never be called if our DrawerLayout and NavigationView have android:fitsSystemWindows="true".
// Therefore to guarantee that it does get called we set it here, because we don't want to letterbox our normal layouts, especially all our landscape views with the gauge views
// Note if you do set it in styles then it should be in values-v28 or even values-v27. Android Studio gives you a warning if you try and set it in values. The problem setting in values-28 is that values-v28
// requires the theme of the activity. Normally our theme is the splash theme and we swap it by calling SetTheme(Resource.Style.OBDTheme) in the first line of OnCreate in the MainActivity.
// Note: Only when devicesWithNotchesAllowFullScreen is true and therefore LayoutInDisplayCutoutMode is ShortEdges will insets.DisplayCutout not be null.
// Whenever LayoutInDisplayCutoutMode it is default or never insets.DisplayCutout will always be null.
// So even if a device has a notch, if devicesWithNotchesAllOwFullScreen is false then will always get Default because DisplayCutout will be null.
// Do we need this? We only need shortEdges if we have a notch, therefore why not wait until the test in OnApplyWindowInsets?
// Answer: We do need it here, because if ShortEdges is not set here, then later in the test in OnApplyWindowInsets, insets.DisplayCutout will be null which will always result in Default being set,
// so we can't avoid this.
// It is really the same as if we had set ShortEdges in styles.xml of values-v28, (which we don't want to do because we are using OBDTheme.Splash). By presetting Window.Attributes.LayoutInDisplayCutoutMode
// here, when the user tells us they want it allows insets.DisplayCutout to be not null by the time we do the test in OnApplyWindowInsets.
// Note the Setting in Preferences has no effect if the device does not have a notch, so no harm is done if a user accidently sets devicesWithNotchesAllowFullscreen to true.
// TODO: Make a note in our user Guide.
#endregion
if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
Window.Attributes.LayoutInDisplayCutoutMode = devicesWithNotchesAllowFullScreen ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
}
#endregion
#region CheckForPreferenceChanges
private void CheckForPreferenceChanges()
{
// Check if anything has been changed in the Settings Fragment before re-reading and updating the preference variables
sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(this);
devicesWithNotchesAllowFullScreen = sharedPreferences.GetBoolean("devicesWithNotchesAllowFullScreen", false);
animateFragments = sharedPreferences.GetBoolean("use_animations", false);
}
#endregion
#region ShowSubscriptionInfoDialog
private void ShowSubscriptionInfoDialog(string title, string explanation)
{
string tag = "SubscriptionInfoDialogFragment";
AndroidX.Fragment.App.FragmentManager fm = SupportFragmentManager;
if (fm != null && !fm.IsDestroyed)
{
AndroidX.Fragment.App.Fragment fragment = fm.FindFragmentByTag(tag);
if (fragment == null)
BasicDialogFragment.NewInstance(title, explanation).Show(fm, tag);
}
}
#endregion
}
}