1
0
mirror of https://github.com/XFox111/GUTSchedule.git synced 2026-04-22 06:58:01 +03:00

Implemented date range filter, code cleanup

This commit is contained in:
Michael Gordeev
2019-12-22 17:41:15 +03:00
parent dba2d0b379
commit 66bf57c7d0
15 changed files with 201 additions and 212 deletions
@@ -1,14 +1,20 @@
using Android.App;
using Android.OS;
using Android.Widget;
using GUT.Schedule.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GUT.Schedule
{
/// <summary>
/// Shows status of schedule export process
/// </summary>
[Activity(Theme = "@style/AppTheme.NoActionBar")]
public class ExportActivity : Activity
{
TextView status;
protected override async void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
@@ -17,24 +23,19 @@ namespace GUT.Schedule
status = FindViewById<TextView>(Resource.Id.status);
status.Text = "Загрузка расписания";
await Parser.LoadSchedule();
List<Subject> schedule = await Parser.LoadSchedule();
schedule = schedule.FindAll(i => i.StartTime.Date >= Data.StartDate && i.StartTime.Date <= Data.EndDate); // Filtering schedule according to export range
status.Text = "Экспортирование в календарь";
int minutes = Data.Reminder switch
{
1 => 0,
2 => 5,
3 => 10,
_ => -1
};
Calendar.Export(Data.Calendars[Data.Calendar].Id, Data.Schedule, minutes < 0 ? (int?)null : minutes, Data.AddTitle);
Calendar.Export(schedule);
status.Text = "Готово";
await Task.Delay(3000);
base.OnBackPressed();
base.OnBackPressed(); // Navigates back to main activity (always because I don't allow backward navigation)
}
public override void OnBackPressed() { }
public override void OnBackPressed() { } // Disables back button
}
}
@@ -6,6 +6,7 @@ using Android.OS;
using Android.Support.V7.App;
using Android.Views;
using Android.Widget;
using GUT.Schedule.Models;
namespace GUT.Schedule
{
@@ -25,9 +26,9 @@ namespace GUT.Schedule
AssignVariables();
AddEvents();
// Settings spinners' dropdown lists content
faculty.SetList(this, Data.Faculties.Select(i => i.Name));
course.SetList(this, "12345".ToCharArray());
course.SetList(this, "1234".ToCharArray());
reminder.SetList(this, new string[]
{
"Нет",
@@ -35,30 +36,12 @@ namespace GUT.Schedule
"За 5 мин",
"За 10 мин"
});
calendar.SetList(this, Data.Calendars.Select(i => i.Name));
calendar.SetList(this, Calendar.Calendars.Select(i => i.Name));
end.Text = Data.EndDate.ToShortDateString();
start.Text = Data.StartDate.ToShortDateString();
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.menu_main, menu);
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
int id = item.ItemId;
if (id == Resource.Id.github)
{
StartActivity(new Intent(Intent.ActionView, Android.Net.Uri.Parse("https://github.com/xfox111/GUTSchedule")));
return true;
}
return base.OnOptionsItemSelected(item);
}
private void Export_Click(object sender, EventArgs e)
{
error.Visibility = ViewStates.Gone;
@@ -69,26 +52,35 @@ namespace GUT.Schedule
error.Visibility = ViewStates.Visible;
}
Data.Faculty = faculty.SelectedItemPosition;
Data.Group = group.SelectedItemPosition;
Data.Course = course.SelectedItemPosition + 1;
Data.Reminder = reminder.SelectedItemPosition;
Data.AddTitle = groupTitle.Checked;
// Forming export parameters
Data.DataSet = new DataSet
{
Faculty = Data.Faculties[faculty.SelectedItemPosition].Id,
Group = Data.Groups[group.SelectedItemPosition].Id,
Course = course.SelectedItemPosition + 1,
AddGroupToTitle = groupTitle.Checked,
Calendar = Calendar.Calendars[calendar.SelectedItemPosition].Id,
Reminder = reminder.SelectedItemPosition switch
{
1 => 0,
2 => 5,
3 => 10,
_ => null
}
};
StartActivity(new Intent(this, typeof(ExportActivity)));
}
private async void End_Click(object sender, EventArgs e)
{
DatePickerFragment picker = new DatePickerFragment();
Data.EndDate = await picker.GetDate(SupportFragmentManager, "datePicker");
Data.EndDate = await new DatePickerFragment().GetDate(SupportFragmentManager, Data.EndDate);
end.Text = Data.EndDate.ToShortDateString();
}
private async void Start_Click(object sender, EventArgs e)
{
DatePickerFragment picker = new DatePickerFragment();
Data.StartDate = await picker.GetDate(SupportFragmentManager, "datePicker");
Data.StartDate = await new DatePickerFragment().GetDate(SupportFragmentManager, Data.StartDate);
start.Text = Data.StartDate.ToShortDateString();
}
@@ -101,8 +93,6 @@ namespace GUT.Schedule
group.SetList(this, Data.Groups.Select(i => i.Name));
}
public override void OnBackPressed() { }
#region Init stuff
private void AssignVariables()
{
@@ -131,5 +121,26 @@ namespace GUT.Schedule
export.Click += Export_Click;
}
#endregion
#region Menu stuff
public override bool OnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.menu_main, menu);
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
if (item.ItemId == Resource.Id.github)
{
StartActivity(new Intent(Intent.ActionView, Android.Net.Uri.Parse("https://github.com/xfox111/GUTSchedule")));
return true;
}
return base.OnOptionsItemSelected(item);
}
#endregion
public override void OnBackPressed() { } // Disables back button
}
}
@@ -13,6 +13,9 @@ using System.Net.Http;
namespace GUT.Schedule
{
/// <summary>
/// Splash screen activity. Loads init data
/// </summary>
[Activity(MainLauncher = true, Theme = "@style/AppTheme.NoActionBar")]
public class StartActivity : AppCompatActivity
{
@@ -40,7 +43,7 @@ namespace GUT.Schedule
private async void Proceed()
{
status.Text = "Загрузка списка доступных для записи календарей";
status.Text = "Загрузка списка календарей";
Calendar.LoadCalendars();
status.Text = "Загрузка списка факультетов";
@@ -56,6 +59,7 @@ namespace GUT.Schedule
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.All(i => i == Permission.Granted))
Proceed();
else
@@ -63,10 +67,11 @@ namespace GUT.Schedule
}
private void RequestPermissions() =>
ActivityCompat.RequestPermissions(this, new string[]
ActivityCompat.RequestPermissions(this, new[]
{
Manifest.Permission.ReadCalendar, Manifest.Permission.WriteCalendar
}, 76);
Manifest.Permission.ReadCalendar,
Manifest.Permission.WriteCalendar
}, 76); // IDK why I need requestCode value to be set (instead of 76 there can be any other number. Anyway it doesn't affect anything)
private void ShowDialog()
{
@@ -79,6 +84,6 @@ namespace GUT.Schedule
dialog.Show();
}
public override void OnBackPressed() { }
public override void OnBackPressed() { } // Disables back button
}
}
@@ -1,19 +0,0 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".
These files will be deployed with your package and will be accessible using Android's
AssetManager, like this:
public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
InputStream input = Assets.Open ("my_asset.txt");
}
}
Additionally, some Android functions will automatically load asset files:
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
+54 -38
View File
@@ -1,21 +1,30 @@
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Database;
using Android.Net;
using Android.Provider;
using Android.Support.V4.Content;
using GUT.Schedule.Models;
using Java.Util;
namespace GUT.Schedule
{
public static class Calendar
{
/// <summary>
/// List of all existing Google calendars on the device
/// </summary>
public static List<(string Id, string Name)> Calendars { get; private set; } = new List<(string Id, string Name)>();
/// <summary>
/// Retrieves all Google Accounts' calendars existing on the device and puts them to <see cref="Calendars"/>
/// </summary>
public static void LoadCalendars()
{
Data.Calendars = new List<(string, string)>();
Calendars = new List<(string, string)>(); // Resetting current calendars list
var calendarsUri = CalendarContract.Calendars.ContentUri;
// Building calendar data retrieval projections
Uri calendarsUri = CalendarContract.Calendars.ContentUri;
string[] calendarsProjection = {
CalendarContract.Calendars.InterfaceConsts.Id,
CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
@@ -24,54 +33,61 @@ namespace GUT.Schedule
CalendarContract.Calendars.InterfaceConsts.AccountType,
};
using Android.Support.V4.Content.CursorLoader loader = new Android.Support.V4.Content.CursorLoader(Application.Context, calendarsUri, calendarsProjection, null, null, null);
// Retrieving calendars data
using CursorLoader loader = new CursorLoader(Application.Context, calendarsUri, calendarsProjection, null, null, null);
ICursor cursor = (ICursor)loader.LoadInBackground();
cursor.MoveToNext();
for (int i = 0; i < cursor.Count; i++)
{
if (cursor.GetString(4) == "com.google" && !cursor.GetString(3).Contains("google"))
Data.Calendars.Add((cursor.GetString(0), $"{cursor.GetString(1)} ({cursor.GetString(2)})"));
if (cursor.GetString(4) == "com.google" && !cursor.GetString(3).Contains("google")) // Loading only users' main calendars
Calendars.Add((cursor.GetString(0), $"{cursor.GetString(1)} ({cursor.GetString(2)})"));
cursor.MoveToNext();
}
}
public static void Export(string calendarId, IEnumerable<Subject> schedule, int? remindBefore, bool addGroupToTitle)
public static void Export(IEnumerable<Subject> schedule)
{
DataSet data = Data.DataSet;
foreach (Subject item in schedule)
AddEvent(calendarId, item, remindBefore, addGroupToTitle);
}
static void AddEvent(string calendarId, Subject subject, int? reminderMinutes, bool addHeader)
{
ContentValues eventValues = new ContentValues();
eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, calendarId);
eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, $"{subject.Order}.{(addHeader ? $" [{subject.Group}]" : "")} {subject.Name} ({subject.Type})");
eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, subject.Professor);
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventLocation, string.Join(';', subject.Cabinets));
eventValues.Put(CalendarContract.Events.InterfaceConsts.Availability, 0);
if(reminderMinutes.HasValue)
eventValues.Put(CalendarContract.Events.InterfaceConsts.HasAlarm, true);
eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, subject.StartTime.ToUnixTime());
eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, Extensions.ToUnixTime(subject.EndTime));
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone, TimeZone.Default.ID);
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventEndTimezone, TimeZone.Default.ID);
Uri response = Application.Context.ContentResolver.Insert(CalendarContract.Events.ContentUri, eventValues);
if (reminderMinutes.HasValue)
{
ContentValues reminderValues = new ContentValues();
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.EventId, long.Parse(response.LastPathSegment));
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Method, 1);
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Minutes, reminderMinutes.Value);
Android.Content.ContentValues eventValues = new Android.Content.ContentValues();
Application.Context.ContentResolver.Insert(CalendarContract.Reminders.ContentUri, reminderValues);
eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, data.Calendar);
eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, string.Format("{0}.{1} {2} ({3})",
item.Order,
data.AddGroupToTitle ? $" [{item.Group}]" : "",
item.Name,
item.Type));
eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, string.Join(';', item.Professor));
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventLocation, string.Join(';', item.Cabinets));
eventValues.Put(CalendarContract.Events.InterfaceConsts.Availability, 0);
if (data.Reminder.HasValue)
eventValues.Put(CalendarContract.Events.InterfaceConsts.HasAlarm, true);
eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, item.StartTime.ToUnixTime());
eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, Extensions.ToUnixTime(item.EndTime));
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone, TimeZone.Default.ID);
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventEndTimezone, TimeZone.Default.ID);
Uri response = Application.Context.ContentResolver.Insert(CalendarContract.Events.ContentUri, eventValues);
if (data.Reminder.HasValue)
{
Android.Content.ContentValues reminderValues = new Android.Content.ContentValues();
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.EventId, long.Parse(response.LastPathSegment));
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Method, 1);
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Minutes, data.Reminder.Value);
Application.Context.ContentResolver.Insert(CalendarContract.Reminders.ContentUri, reminderValues);
}
}
}
}
@@ -1,40 +0,0 @@
using System;
using Android.OS;
using Android.Widget;
using Android.Support.V4.App;
using System.Threading.Tasks;
namespace GUT.Schedule
{
public class DatePickerFragment : DialogFragment, Android.App.DatePickerDialog.IOnDateSetListener
{
DateTime date;
bool dismissed = false;
public override Android.App.Dialog OnCreateDialog(Bundle savedInstanceState)
{
DateTime now = DateTime.Today;
return new Android.App.DatePickerDialog(Activity, this, now.Year, now.Month - 1, now.Day);
}
public void OnDateSet(DatePicker view, int year, int month, int dayOfMonth)
{
SetDate(view.DateTime);
}
public async Task<DateTime> GetDate(FragmentManager manager, string tag)
{
Show(manager, tag);
while (!dismissed)
await Task.Delay(500);
return date;
}
private void SetDate(DateTime date)
{
this.date = date;
dismissed = true;
}
}
}
+21
View File
@@ -8,11 +8,25 @@ namespace GUT.Schedule
{
public static class Extensions
{
/// <summary>
/// Sets array as Spinner dropdown list content
/// </summary>
/// <typeparam name="T">Array items type</typeparam>
/// <param name="spinner">Spinner on which array will be assigned to</param>
/// <param name="context">Current activity context. In most common cases <c>this</c> will do</param>
/// <param name="array">Array of items to be displayed</param>
public static void SetList<T>(this Spinner spinner, Context context, IEnumerable<T> array)
{
ArrayAdapter adapter = new ArrayAdapter(context, Resource.Layout.support_simple_spinner_dropdown_item, array.ToList());
spinner.Adapter = adapter;
}
/// <summary>
/// Returns <see cref="DateTime"/> instance based on study week number, weekday and semester start day number
/// </summary>
/// <param name="week">Number of the study week</param>
/// <param name="weekday">Weekday</param>
/// <returns><see cref="DateTime"/> instance based on study week number, weekday and semester start day number</returns>
public static DateTime GetDateFromWeeks(int week, int weekday)
{
DateTime dt = new DateTime(DateTime.Today.Year, 9, Data.FirstWeekDay);
@@ -22,6 +36,13 @@ namespace GUT.Schedule
return dt;
}
/// <summary>
/// Converts <see cref="DateTime"/> to milliseconds count
/// </summary>
/// <remarks>In the nearest future we will be fucked because of that shit</remarks>
/// <param name="dt"><see cref="DateTime"/> which is to be converted to UNIX time</param>
/// <returns><see cref="long"/> which is represented by total milliseconds count passed since 1970</returns>
public static long ToUnixTime(this DateTime dt) =>
(long)dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
}
@@ -0,0 +1,43 @@
using System;
using Android.OS;
using Android.Widget;
using Android.App;
using System.Threading.Tasks;
namespace GUT.Schedule
{
/// <summary>
/// Date picker
/// </summary>
public class DatePickerFragment : Android.Support.V4.App.DialogFragment, DatePickerDialog.IOnDateSetListener
{
DateTime _date;
bool dismissed = false;
public override Dialog OnCreateDialog(Bundle savedInstanceState) =>
new DatePickerDialog(Activity, this, _date.Year, _date.Month - 1, _date.Day);
// Occures when user selected a date
public void OnDateSet(DatePicker view, int year, int month, int dayOfMonth)
{
_date = view.DateTime;
dismissed = true;
}
/// <summary>
/// Shows date picker and waits for user input
/// </summary>
/// <param name="manager">Fragment manager of the current activity (In most common cases it is <c>this.FragmentManager</c>)</param>
/// <param name="date">Date which is to be selected by default</param>
/// <returns><see cref="DateTime"/> picked by user</returns>
public async Task<DateTime> GetDate(Android.Support.V4.App.FragmentManager manager, DateTime date)
{
_date = date;
Show(manager, "datePicker");
while (!dismissed)
await Task.Delay(100);
return date;
}
}
}
@@ -60,21 +60,20 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Calendar.cs" />
<Compile Include="Data.cs" />
<Compile Include="DatePickerFragment.cs" />
<Compile Include="ExportActivity.cs" />
<Compile Include="Models\Data.cs" />
<Compile Include="Fragments\DatePickerFragment.cs" />
<Compile Include="Activities\ExportActivity.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="MainActivity.cs" />
<Compile Include="Activities\MainActivity.cs" />
<Compile Include="Models\DataSet.cs" />
<Compile Include="Parser.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StartActivity.cs" />
<Compile Include="Subject.cs" />
<Compile Include="Activities\StartActivity.cs" />
<Compile Include="Models\Subject.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Properties\AndroidManifest.xml" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\activity_main.xml">
@@ -1,4 +1,5 @@
using System;
using GUT.Schedule.Models;
using System;
using System.Collections.Generic;
namespace GUT.Schedule
@@ -7,17 +8,13 @@ namespace GUT.Schedule
{
public static List<(string Id, string Name)> Faculties { get; set; }
public static List<(string Id, string Name)> Groups { get; set; }
public static List<(string Id, string Name)> Calendars { get; set; }
public static List<Subject> Schedule { get; set; }
public static int FirstWeekDay { get; set; }
public static DateTime StartDate { get; set; } = DateTime.Today;
public static DateTime EndDate { get; set; } = DateTime.Today.AddDays(7);
public static int Faculty { get; set; }
public static int Group { get; set; }
public static int Course { get; set; }
public static int Calendar { get; set; }
public static int Reminder { get; set; }
public static bool AddTitle { get; set; }
/// <summary>
/// Export parameters
/// </summary>
public static DataSet DataSet { get; set; }
}
}
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace GUT.Schedule
namespace GUT.Schedule.Models
{
public class Subject
{
@@ -1,44 +0,0 @@
Images, layout descriptions, binary blobs and string dictionaries can be included
in your application as resource files. Various Android APIs are designed to
operate on the resource IDs instead of dealing with images, strings or binary blobs
directly.
For example, a sample Android app that contains a user interface layout (main.xml),
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
would keep its resources in the "Resources" directory of the application:
Resources/
drawable/
icon.png
layout/
main.xml
values/
strings.xml
In order to get the build system to recognize Android resources, set the build action to
"AndroidResource". The native Android APIs do not operate directly with filenames, but
instead operate on resource IDs. When you compile an Android application that uses resources,
the build system will package the resources for distribution and generate a class called "R"
(this is an Android convention) that contains the tokens for each one of the resources
included. For example, for the above Resources layout, this is what the R class would expose:
public class R {
public class drawable {
public const int icon = 0x123;
}
public class layout {
public const int main = 0x456;
}
public class strings {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
to reference the layout/main.xml file, or R.strings.first_string to reference the first
string in the dictionary file values/strings.xml.
@@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/github"
android:orderInCategory="100"
android:title="Посмотреть исходный код на GitHub"
app:showAsAction="never" />
</menu>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@color/colorPrimary"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@color/colorPrimary"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>