Skip to content

speedypleath/Stopor

Repository files navigation

Stopor

Android/IOS application written in Flutter that helps you find events and organize events. Demo: https://www.youtube.com/watch?v=fctKgZ37ySY

App description

1. User stories

  1. As a user, I want to be able to search for events
  2. As a user, I want to be notified when an event I follow is edited
  3. As a user, I want to be able to follow artists
  4. As a user, I want to be notified when 24 hours before an event
  5. As an artist/organiser, I want to be able to post comments on my event
  6. As a user I want to be able to save events
  7. As a user/tourist, I want to be able to find events near me
  8. As a artist I want my events to be categorised by genre
  9. As a user, I want to be notified when an artist will organise an event near me
  10. As an organizer, I want to be able to plan an event
  11. As a user/tourist I want to know the location of an event
  12. As an organizer I want to be able to edit an event
  13. As an artist/venue manager/organiser I want to be able to add a description to my events
  14. As a user, I want to be able to link my spotify/facebook account

2. Backlog

We monitorized our backlog creation using Jira.

Boards

Boards

Roadmap

Roadmap

First sprint

First sprint

3. Features List

  • Syncronize Facebook events

  • Syncronize Spotify artists

  • Search event, artist, user

  • Follow event, artist, user

  • Filter main screen by top events, followed and nearby

  • Add/Edit event

  • Update account info

UML diagram

Software_Design

4. Firestore documents

  • artists
  • events
  • user
  • following
  • participation

5. API's used

  • Facebook Graph API (for synchronizing events)
  • Spotify API (for synchronizing artists)
  • Algolia (for indexing search)

6. Screens

News feed

Settings

Settings Settings

Add event

Select event type Add event Select location Select image

View event

View event View event as owner Add artist to event

Search page

Design patterns

Singleton (Factory Constructor)

  DatabaseService._privateConstructor();
  static final DatabaseService _instance =
      DatabaseService._privateConstructor();
  factory DatabaseService() {
    return _instance;
  }

Provider

in main.dart

    return MultiProvider(
      providers: [
        Provider<AuthenticationService>(
          create: (context) => AuthenticationService(FirebaseAuth.instance),
        ),
        StreamProvider(
          create: (context) =>
              context.read<AuthenticationService>().authStateChanges,
          initialData: null,
        ),
      ],

in authentication_wrapper.dart

class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
  @override
  Widget build(BuildContext context) {
    final firebaseUser = context.watch<User>();

    if (firebaseUser != null) return BottomNav();
    return LoginScreen();
  }
}

Strategy pattern

class Mapper {
  var map;

  setArtist() {
    map = ArtistMap().map;
  }

  setUser() {
    map = UserMap().map;
  }

  setEvent() {
    map = EventMap().map;
  }

  T cast<T>(x) => x is T ? x : null;

  Future<List<T>> mapToObjectList<T>(data) async {
    List<T> objects = [];
    try {
      await data.forEach((element) async {
        T object = cast<T>(await map(element.data(), element.id));
        objects.add(object);
      });
      return objects;
    } catch (e) {
      print(e.toString());
      return null;
    }
  }
}

abstract class MapStrategy {
  map(data, id);
}

class EventMap implements MapStrategy {
  Future<Event> map(data, id) async {
    try {
      String organiser = data["organiser"];
      bool isOnline;
      String location;
      if (data["location"] == null)
        location = "";
      else if (!(data['location'] is String))
        location = data["location"]["name"];
      else
        location = data["location"];
      if (data["isOnline"] == null)
        isOnline = false;
      else
        isOnline = data["isOnline"];
      String facebookId = data["facebookId"] == null ? "" : data["facebookId"];
      return new Event(
        id: id,
        description: data["description"],
        date: DateTime(2020, 9, 17, 17, 30),
        name: data["name"],
        eventImage: data["image"] != false
            ? data["image"]
            : "https://keysight-h.assetsadobe.com/is/image/content/dam/keysight/en/img/prd/ixia-homepage-redirect/network-visibility-and-network-test-products/Network-Test-Solutions-New.jpg",
        location: location,
        isOnline: isOnline,
        facebookId: facebookId,
        organiser: organiser,
      );
    } catch (e) {
      print(e.toString());
      return null;
    }
  }
}

class ArtistMap implements MapStrategy {
  Future<Artist> map(data, id) async {
    try {
      var genres = new Map<String, bool>.from(data["genres"]);
      return new Artist(
        id: id,
        name: data["name"],
        spotifyId: data["spotifyId"],
        genres: genres,
        image: data["image"],
      );
    } catch (e) {
      print(e.toString());
      return null;
    }
  }
}

class UserMap implements MapStrategy {
  Future<User> map(data, id) async {
    try {
      return User(
          email: data["email"],
          id: id,
          pfp: data["profilePic"],
          name: data["name"],
          facebookAuthToken: data["authToken"],
          spotifyAuthToken: data["spotifyAuthToken"]);
    } catch (e) {
      print(e.toString());
      return null;
    }
  }
}

Source control

Unit testing

Unit testing security rules for firestore

Authentication

describe("Authentication", () => {
   it("Can't read from database if not authenticated", async () => {
       const db = getFirestore();
       const testDoc = db.collection("randomCollection").doc("testDoc");
       await firebase.assertFails(testDoc.get());
   });

   it("Can't write to database if not authenticated", async () => {
       const db = firebase.initializeTestApp({ projectId: PROJECT_ID }).firestore();
       const testDoc = db.collection("randomCollection").doc("testDoc");
       await firebase.assertFails(testDoc.set({ foo: "bar" }));
   });

   it("Can't read from database if authenticated doesn't exist", async () => {
       const db = getFirestore(USER);
       const testDoc = db.collection("randomCollection").doc("testDoc");
       await firebase.assertFails(testDoc.get());
   });

   it("Can read from database if authenticated", async () => {
       const db = getFirestore(USER);
       const testDoc = db.collection("randomCollection").doc("testDoc");
       const userDoc = db.collection("users").doc("speedypleath");
       await firebase.assertSucceeds(userDoc.set({userId: "speedypleath"}));
       await firebase.assertSucceeds(testDoc.get());
   });

   after(async () => {
       const db = getFirestore(USER);
       const testDoc = db.collection("users").doc("speedypleath");
       await testDoc.delete();
   });
});

Users

describe("Users", () => {
    it("Can write user if it does correspond to authenticated user", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await firebase.assertSucceeds(testDoc.set({userId: "speedypleath"}));
    });

    it("Can't write user if it doesn't correspond to authenticated user", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("users").doc("anotherTestDoc");
        await firebase.assertFails(testDoc.set({ userId: "foo" }));
    });

    it("Can't modify user if it doesn't correspond to authenticated user", async () => {
        const db = getFirestore(ANOTHER_USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await firebase.assertFails(testDoc.update({ userId: "speedypleath" }));
    });

    it("Can modify user if it does correspond to authenticated user", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await firebase.assertSucceeds(testDoc.update({ bar: "foo", userId: "speedypleath"}));
    });

    it("Can't delete user if it doesn't correspond to authenticated user", async () => {
        const db = getFirestore(ANOTHER_USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await firebase.assertFails(testDoc.delete());
    });

    it("Can delete user if it does correspond to authenticated user", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await firebase.assertSucceeds(testDoc.delete());
    });

    after(async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await firebase.assertSucceeds(testDoc.set({userId: "speedypleath"}));
    });
});

Events

describe("Events", () => {
    it("Can write to events if authenticated", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("events").doc("testDoc");
        await firebase.assertSucceeds(testDoc.set({organiser: "speedypleath", facebookId: "123"}));
    });

    it("Can't modify event if authenticated user is not the owner of requested event", async () => {
        const db = getFirestore(ANOTHER_USER);
        const testDoc = db.collection("events").doc("testDoc");
        await firebase.assertFails(testDoc.update({ foo: "bar" }));
    });

    it("Can modify event if authenticated user is the owner of requested event", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("events").doc("testDoc");
        await firebase.assertSucceeds(testDoc.update({ bar: "foo", facebookId: "123", organiser: "speedypleath"}));
    });

    it("Can't delete event if authenticated user is not the owner of requested event", async () => {
        const db = getFirestore(ANOTHER_USER);
        const testDoc = db.collection("events").doc("testDoc");
        await firebase.assertFails(testDoc.delete());
    });

    it("Can delete event if authenticated user is the owner of requested event", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("events").doc("testDoc");
        await firebase.assertSucceeds(testDoc.delete());
    });
});

Artists

describe("Artists", () => {
    it("Can write to artist if authenticated", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("artists").doc("testDoc");
        await firebase.assertSucceeds(testDoc.set({userId: "speedypleath", spotifyId: "123"}));
    });

    it("Can't modify artist authenticated user id is not equal to artist's organiser", async () => {
        const db = getFirestore(ANOTHER_USER);
        const testDoc = db.collection("artists").doc("testDoc");
        await firebase.assertFails(testDoc.update({ foo: "bar" }));
    });

    it("Can modify artist if authenticated user id is not equal to artist's organiser", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("artists").doc("testDoc");
        await firebase.assertSucceeds(testDoc.update({ bar: "foo", spotifyId: "123", organiser: "speedypleath"}));
    });

    it("Can't delete artist if authenticated user id is not equal to artist's organiser", async () => {
        const db = getFirestore(ANOTHER_USER);
        const testDoc = db.collection("artists").doc("testDoc");
        await firebase.assertFails(testDoc.delete());
    });

    it("Can delete artist if authenticated user id is equal to artist's organiser", async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("artists").doc("testDoc");
        await firebase.assertSucceeds(testDoc.delete());
    });

    after(async () => {
        const db = getFirestore(USER);
        const testDoc = db.collection("users").doc("speedypleath");
        await testDoc.delete();
    });
});

Results

image

Bug reporting

Fixed issues

Build tools

Stopor is developed using flutter, which has a Gradle-based building tool. There are two options available to build the app:

  1. Build an app bundle
    flutter build appbundle
  2. Build an APK
    flutter build apk --split-per-abi

Flutter can also install an APK directly on a connected Android device, using

flutter install

About

Android/IOS application that helps you find and organize events.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages