TheAlmightyBob / Calendars

Cross-platform calendar API plugin for Xamarin and Windows
MIT License
101 stars 23 forks source link

Exact the same issue IOS calendar hidden when Exchange account #22

Open svandelaarschot opened 8 years ago

svandelaarschot commented 8 years ago

i tried to fix it but i don't understood the project .SLN since when i reference the 2 dll files Calendar.Plugin.dll and Calendars.Plugin.Abstractions.dll to every project Portable Forms, Android, iOS i got the error message:

internal static Exception NotImplementedInReferenceAssembly()
{
  return new NotImplementedException("This functionality is not implemented in the portable version of this assembly.  You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.");
}

from CrossCalendars.cs

i want to contribute but how does it work? since i can only make it work when installing on nuget.

Idea of the FIX:

Let the people on iOS choose the source they want like Exchange, Local or iCloud

svandelaarschot commented 8 years ago

Trying to help where i can :) so im looking to get the project running :) and build.

TheAlmightyBob commented 8 years ago

Hi! Thanks for your help!

So, saying that you're referencing "Calendar.Plugin.dll" is actually kind of vague, as there are a few of those. This uses the "PCL Bait & Switch" technique (searching for that term will turn up some explanations, but it's also the standard technique used by almost all Xamarin Plugins).

Basically, that error is because you are referencing the PCL stub class directly, when you actually need to be referencing the platform-specific versions. If you look at the unit test projects, you can see that they just reference the platform-specific projects directly. When installing via NuGet, it automatically takes care of this for you.

Since you mention Portable Forms and are trying to work on this, I'm guessing that you're trying to reference the projects directly from a Xamarin.Forms projects so you can test and debug into them? In which case I believe you can reference Calendars.Plugin from Portable Forms, but then in the iOS/Android projects reference the .Android/.iOSUnified versions instead.

You still want the Abstractions referenced by everything, there are no tricks to that one.

TheAlmightyBob commented 8 years ago

Also, it seemed from a previous message like you had tried just adding the calendar to the Exchange source as part of the existing logic, but decided that it didn't work? I didn't quite follow what the problem was there.

You suggest to "Let the people on iOS choose the source they want." I'm open to such an idea in cases where the iOS Calendar app would actually display multiple "Add Calendar" buttons for different sources, but I wouldn't want to offer a choice of "Local" if that's only going to result in a hidden calendar...

svandelaarschot commented 8 years ago

Hey Caleb, Yes today i was struggeling with the plugin since i need it @t my work for the waste management APP people going to put their waste collection days into the native calendar.

Someone came up and asked me why they can't see the events into their 'Account' because they had multiple exchange accounts on their iPad.

So we need to find a way that the user can select their own 'Agenda' / 'Account'.

Its weird from apple if you look at the Android version of the Plugin it just creates a new account into they Calendar itselfs wich is nice. and understandable. compared to iOS

TheAlmightyBob commented 8 years ago

So is it successfully adding the calendar to one of those exchange accounts, and the problem is that it's choosing the "wrong" one?

Or is it failing to add the calendar to either account?

And yes this is an annoying undocumented quirk of iOS...

svandelaarschot commented 8 years ago

Im making a Xamarin.Forms version of yours :) keep ya up to date.

svandelaarschot commented 8 years ago

FIXED ! Always assign the calendar in CalendarSource: Subscribed Calendars and the problem with hidden calendars is gone.

svandelaarschot commented 8 years ago

Caleb,

Create a property in Asbtractions.Calendar

        /// <summary>
        /// The Account of the calendar
        /// </summary>
        public String Source { get; set; }

and when its filled with "Subscribed Calendars" for example than take source: Subscribed Calendars else do your loops through source :)

public async Task AddOrUpdateCalendarAsync(Calendar calendar)
        {
            InitEventStore();
            await RequestCalendarAccess().ConfigureAwait(false);

            EKCalendar deviceCalendar = null;
            EKSource Source = null;

            if(!String.IsNullOrEmpty(calendar.Identifier))
            {
                deviceCalendar = _eventStore.GetCalendar(calendar.Identifier);

                if (!String.IsNullOrEmpty(calendar.Source))
                {
                    /* Going for the save way ! */
                    foreach (var item in _eventStore.Sources)
                    {
                        if (item.Title == calendar.Source)
                        {
                            Source = item;
                            break;
                        }
                    }
                }

                deviceCalendar.Source = Source;

                if (deviceCalendar == null)
                {
                    throw new ArgumentException("Specified calendar does not exist on device", nameof(calendar));
                }
            }

            if (deviceCalendar == null)
            {

                if (!String.IsNullOrEmpty(calendar.Source))
                {
                    /* Going for the save way ! */
                    foreach (var item in _eventStore.Sources)
                    {
                        if (item.Title == calendar.Source)
                        {
                            Source = item;
                            break;
                        }
                    }
                }

                deviceCalendar = CreateEKCalendar(calendar.Title, calendar.Color, Source);
                deviceCalendar.Source = Source;
                calendar.Identifier = deviceCalendar.CalendarIdentifier;

                // Update color in case iOS assigned one
                if (deviceCalendar?.CGColor != null)
                { calendar.Color = ColorConversion.ToHexColor(deviceCalendar.CGColor); }
            }
            else
            {
                deviceCalendar.Title = calendar.Title;

                if (!string.IsNullOrEmpty(calendar.Color))
                {
                    deviceCalendar.CGColor = ColorConversion.ToCGColor(calendar.Color);
                }

                NSError error = null;
                if (!_eventStore.SaveCalendar(deviceCalendar, true, out error))
                {
                    // Without this, the eventStore will continue to return the "updated"
                    // calendar even though the save failed!
                    // (this obviously also resets any other changes, but since we own the eventStore
                    //  we can be pretty confident that won't be an issue)
                    //
                    _eventStore.Reset();

                    if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.CalendarIsImmutable)
                    {
                        throw new ArgumentException(error.LocalizedDescription, new NSErrorException(error));
                    }
                    else
                    {
                        throw new PlatformException(error.LocalizedDescription, new NSErrorException(error));
                    }
                }
            }
        }

` private EKCalendar CreateEKCalendar(string calendarName, string color = null, EKSource Source = null)
        {
            // Log the available sources
            //
            System.Console.WriteLine("Sources:");

            foreach (var source in _eventStore.Sources)
            {
                System.Console.WriteLine($"{source.Title}, {source.SourceType}");
            }

            if (Source == null)
            {
                // first attempt to find any and all iCloud sources
                //
                var iCloudSources = _eventStore.Sources.Where(s => s.SourceType == EKSourceType.CalDav &&
                                                              s.Title.Equals("icloud", StringComparison.InvariantCultureIgnoreCase));
                var cal = SaveEKCalendar(iCloudSources, calendarName, color);
                if (cal != null)
                {
                    return cal;
                }

                // other sources that we didn't try before that are CalDav
                // (may be renamed iCloud)
                //
                var otherSources = _eventStore.Sources.Where(s => s.SourceType == EKSourceType.CalDav &&
                                                             !s.Title.Equals("icloud", StringComparison.InvariantCultureIgnoreCase));
                cal = SaveEKCalendar(otherSources, calendarName, color);
                if (cal != null)
                {
                    return cal;
                }

                // finally attempt just local sources
                //
                var localSources = _eventStore.Sources.Where(s => s.SourceType == EKSourceType.Local);
                cal = SaveEKCalendar(localSources, calendarName, color, true);
                if (cal != null)
                {
                    return cal;
                }

            }
            else
            {
                var cal = SaveEKCalendar(Source, calendarName, color);
                if (cal != null)
                {
                    return cal;
                }
            }

            throw new InvalidOperationException("No active calendar sources available to create calendar on.");
        }
`
svandelaarschot commented 8 years ago

Maybe some code cleanups after trying :) to make it nice :)

svandelaarschot commented 8 years ago

Caleb,

Can you add above code to your code and place it on nuget ? than we have it crossplatform

the property:

  /// <summary>
    /// The Account of the calendar
    /// </summary>
    public String Source { get; set; }

Can best be named iOSSource since its ios only behaviour

Kind Regards, Stefan.

TheAlmightyBob commented 8 years ago

Thanks Stefan.

  1. If you would like to submit code to the project, please submit a pull request. (makes it easier to review/discuss the code, track history, etc)
  2. I'm still trying to figure out what this is doing and why. I didn't see an answer to my previous question, "So is it successfully adding the calendar to one of those exchange accounts, and the problem is that it's choosing the wrong one? Or is it failing to add the calendar to either account?" Does setting the "Subscribed" source somehow add it to Exchange? Or somewhere else? Did you already have other Subscribed calendars?
  3. If we need to support specifying the iOS calendar source, that suggests that we may need to support retrieving the available calendar sources. If it's going to be a property of Calendar, then it probably need to be filled in by the GetCalendar functions.... (and alternative could be to make it a parameter to AddOrUpdateCalendar...)
svandelaarschot commented 8 years ago

The source Property for Calendars.cs is because you can choose out of exchange accounts or Subscribed calendars but for most of the time Subscribed calendars is the better way to go.

Param for AddOrUpdateCalendar can be but than you need to know the source.

i will try to make a pull request,

svandelaarschot commented 8 years ago

I cant make pull requests since you dont gief me the rights i think

TheAlmightyBob commented 8 years ago

You can make pull requests. You can't directly push changes to this repo. You need to create a fork in GitHub, commit your changes there.

Why is Subscribed the better way to go?