├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Info.plist
│ └── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
├── Runner.xcworkspace
│ └── contents.xcworkspacedata
└── Runner.xcodeproj
│ ├── project.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ └── Runner.xcscheme
├── lib
├── models
│ ├── models.dart
│ ├── feed.dart
│ ├── subreddit.dart
│ ├── photo.dart
│ ├── feed.g.dart
│ └── subreddit.g.dart
├── api
│ ├── response_models
│ │ ├── response_models.dart
│ │ ├── serializers.dart
│ │ ├── serializers.g.dart
│ │ ├── subreddit_response.dart
│ │ └── link_listing_response.dart
│ ├── api.dart
│ ├── mappers
│ │ ├── subreddit_info_mapper.dart
│ │ └── link_listing_photos_mapper.dart
│ ├── api_repository
│ │ ├── local.dart
│ │ ├── facade.dart
│ │ └── api.dart
│ └── reddit_repository.dart
├── consts.dart
├── screens
│ ├── screens.dart
│ ├── photo_preview.dart
│ ├── feed_tab.dart
│ ├── preferences_sheet.dart
│ ├── main.dart
│ └── import_subscriptions.dart
├── widgets
│ ├── state_aware
│ │ ├── preference_view_model.dart
│ │ ├── show_nsfw_preference_tile.dart
│ │ └── dark_theme_preference_tile.dart
│ ├── reddigram_logo.dart
│ ├── widgets.dart
│ ├── nsfw_badge.dart
│ ├── subreddit_circle_avatar.dart
│ ├── preferences_provider.dart
│ ├── subreddit_list_tile.dart
│ ├── nsfw_overlay.dart
│ ├── infinite_list.dart
│ ├── icon_navigation_bar.dart
│ ├── upvoteable.dart
│ └── photo_grid_item.dart
├── utils
│ └── jwt.dart
├── store
│ ├── auth
│ │ ├── reducer.dart
│ │ ├── auth_state.dart
│ │ ├── actions.dart
│ │ └── auth_state.g.dart
│ ├── suggested_subscriptions
│ │ ├── reducer.dart
│ │ └── actions.dart
│ ├── subreddits
│ │ ├── reducer.dart
│ │ └── actions.dart
│ ├── subreddits_search
│ │ ├── subreddits_search_state.dart
│ │ ├── reducer.dart
│ │ ├── actions.dart
│ │ └── subreddits_search_state.g.dart
│ ├── reducer.dart
│ ├── store.dart
│ ├── feeds
│ │ ├── reducer.dart
│ │ └── actions.dart
│ ├── preferences
│ │ ├── preferences_state.dart
│ │ ├── reducer.dart
│ │ ├── actions.dart
│ │ └── preferences_state.g.dart
│ ├── photos
│ │ ├── actions.dart
│ │ └── reducer.dart
│ ├── subscriptions
│ │ ├── reducer.dart
│ │ └── actions.dart
│ ├── app_state.dart
│ └── app_state.g.dart
├── main.dart
├── theme.dart
└── app.dart
├── media
└── screens_preview.jpg
├── assets
├── chrome_desktop_site.png
└── fonts
│ └── Pacifico
│ ├── Pacifico-Regular.ttf
│ └── OFL.txt
├── android
├── gradle.properties
├── app
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── values
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable
│ │ │ │ ├── launch_background.xml
│ │ │ │ └── ic_file_download.xml
│ │ │ └── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── kotlin
│ │ │ └── me
│ │ │ │ └── wolszon
│ │ │ │ └── reddigram
│ │ │ │ ├── ReddigramApplication.kt
│ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ ├── google-services.json
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── build.gradle
├── test
├── repository_test.dart
└── jwt_utils_test.dart
├── .metadata
├── README.md
├── pubspec.yaml
├── .gitignore
└── LICENSE
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/lib/models/models.dart:
--------------------------------------------------------------------------------
1 | export 'photo.dart';
2 | export 'feed.dart';
3 | export 'subreddit.dart';
--------------------------------------------------------------------------------
/media/screens_preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/media/screens_preview.jpg
--------------------------------------------------------------------------------
/assets/chrome_desktop_site.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/assets/chrome_desktop_site.png
--------------------------------------------------------------------------------
/assets/fonts/Pacifico/Pacifico-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/assets/fonts/Pacifico/Pacifico-Regular.ttf
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/lib/api/response_models/response_models.dart:
--------------------------------------------------------------------------------
1 | export 'link_listing_response.dart';
2 | export 'serializers.dart';
3 | export 'subreddit_response.dart';
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/lib/consts.dart:
--------------------------------------------------------------------------------
1 | class GlanceConsts {
2 | static const String oauthClientId = 'kLJBLghY79EbfQ';
3 | static const String oauthRedirectUrl = 'reddigram://redirect';
4 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Albert221/Glance/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/screens/screens.dart:
--------------------------------------------------------------------------------
1 | export 'feed_tab.dart';
2 | export 'import_subscriptions.dart';
3 | export 'main.dart';
4 | export 'photo_preview.dart';
5 | export 'preferences_sheet.dart';
6 | export 'subreddit.dart';
7 | export 'subscriptions_tab.dart';
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
7 |
--------------------------------------------------------------------------------
/test/repository_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:reddigram/api/api.dart';
3 |
4 | void main() {
5 | test('Repository fetches and maps stuff', () async {
6 | final listing = await RedditRepository().feed('r/EarthPorn');
7 |
8 | print(listing);
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 88fa7ea4031f5c86225573e58e5558dc4ea1c251
8 | channel: beta
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/lib/widgets/state_aware/preference_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | class PreferenceViewModel {
4 | final T value;
5 | final void Function(bool) onSwitch;
6 |
7 | PreferenceViewModel({@required this.value, @required this.onSwitch})
8 | : assert(value != null),
9 | assert(onSwitch != null);
10 | }
11 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Flutter Wrapper
2 | -keep class io.flutter.app.** { *; }
3 | -keep class io.flutter.plugin.** { *; }
4 | -keep class io.flutter.util.** { *; }
5 | -keep class io.flutter.view.** { *; }
6 | -keep class io.flutter.** { *; }
7 | -keep class io.flutter.plugins.** { *; }
8 |
9 | -dontwarn android.arch.lifecycle.DefaultLifecycleObserver
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_file_download.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/test/jwt_utils_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:reddigram/utils/jwt.dart';
3 |
4 | void main() {
5 | test('Repository fetches and maps stuff', () async {
6 | final token =
7 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTYzNzk1Mjd9.ahhv8_mBIYfdnrTUqyrgY4ABT5riU-_xACYQ0hQ0fcI';
8 | expect(jwtExp(token), DateTime.utc(2019, 4, 27, 15, 38, 47));
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/lib/widgets/reddigram_logo.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ReddigramLogo extends StatelessWidget {
4 | const ReddigramLogo({Key key}) : super(key: key);
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | return Text(
9 | 'Glance',
10 | style: TextStyle(
11 | fontFamily: 'Pacifico',
12 | fontSize: 22.0,
13 | fontWeight: FontWeight.normal,
14 | ),
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/api/api.dart:
--------------------------------------------------------------------------------
1 | export 'reddit_repository.dart';
2 |
3 | export 'api_repository/api.dart';
4 | export 'api_repository/local.dart';
5 | export 'api_repository/facade.dart';
6 |
7 | export 'mappers/link_listing_photos_mapper.dart';
8 | export 'mappers/subreddit_info_mapper.dart';
9 |
10 | import 'package:reddigram/api/api.dart';
11 |
12 | final redditRepository = RedditRepository();
13 | final apiRepository = ApiRepositoriesFacade(
14 | fetchRedditAccessToken: redditRepository.getAccessToken,
15 | );
16 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/api/response_models/serializers.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_value/standard_json_plugin.dart';
2 | import 'package:built_value/serializer.dart';
3 | import 'package:built_collection/built_collection.dart';
4 | import 'package:reddigram/api/response_models/response_models.dart';
5 |
6 | part 'serializers.g.dart';
7 |
8 | @SerializersFor(const [
9 | LinkListingResponse,
10 | SubredditListResponse,
11 | SubredditListResponse,
12 | ])
13 | final Serializers serializers =
14 | (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
15 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/lib/utils/jwt.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:jaguar_jwt/jaguar_jwt.dart';
4 |
5 | DateTime jwtExp(String token) {
6 | try {
7 | final split = token.split('.');
8 | final encodedPayload = split[1];
9 | final decodedPayload = B64urlEncRfc7515.decodeUtf8(encodedPayload);
10 |
11 | final payload = json.decode(decodedPayload);
12 |
13 | return DateTime.fromMillisecondsSinceEpoch(
14 | payload['exp'] * 1000,
15 | isUtc: true,
16 | );
17 | } on Exception catch (_) {
18 | return null;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'state_aware/dark_theme_preference_tile.dart';
2 | export 'state_aware/show_nsfw_preference_tile.dart';
3 | export 'state_aware/preference_view_model.dart';
4 | export 'icon_navigation_bar.dart';
5 | export 'infinite_list.dart';
6 | export 'nsfw_badge.dart';
7 | export 'nsfw_overlay.dart';
8 | export 'photo_grid_item.dart';
9 | export 'photo_list_item.dart';
10 | export 'preferences_provider.dart';
11 | export 'reddigram_logo.dart';
12 | export 'subreddit_circle_avatar.dart';
13 | export 'subreddit_list_tile.dart';
14 | export 'upvoteable.dart';
15 |
--------------------------------------------------------------------------------
/lib/store/auth/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:reddigram/store/store.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | Reducer authStateReducer = combineReducers([
5 | TypedReducer(_setUsername),
6 | TypedReducer(_setAuthStatus),
7 | ]);
8 |
9 | AuthState _setUsername(AuthState state, SetUsername action) {
10 | return state.rebuild((b) => b..username = action.username);
11 | }
12 |
13 | AuthState _setAuthStatus(AuthState state, SetAuthStatus action) {
14 | return state.rebuild((b) => b..status = action.status);
15 | }
16 |
--------------------------------------------------------------------------------
/lib/store/suggested_subscriptions/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:reddigram/store/store.dart';
3 | import 'package:redux/redux.dart';
4 |
5 | Reducer> suggestedSubscriptionsReducer = combineReducers([
6 | TypedReducer, FetchedSuggestedSubscriptions>(
7 | _fetchedSuggestedSubscriptions),
8 | ]);
9 |
10 | BuiltSet _fetchedSuggestedSubscriptions(
11 | BuiltSet state, FetchedSuggestedSubscriptions action) {
12 | return BuiltSet.from(action.suggestedSubscriptions);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/models/feed.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:built_value/built_value.dart';
3 |
4 | part 'feed.g.dart';
5 |
6 | abstract class Feed implements Built {
7 | String get name;
8 |
9 | BuiltList get photosIds;
10 |
11 | Feed._();
12 |
13 | factory Feed([updates(FeedBuilder b)]) {
14 | return _$Feed._(
15 | name: '',
16 | photosIds: BuiltList(),
17 | )
18 | .rebuild(updates);
19 | }
20 |
21 | factory Feed.blank(String name) {
22 | return Feed().rebuild((b) => b..name = name);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/store/subreddits/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:reddigram/models/models.dart';
3 | import 'package:reddigram/store/store.dart';
4 | import 'package:redux/redux.dart';
5 |
6 | Reducer> subredditsReducer = combineReducers([
7 | TypedReducer, FetchedSubreddits>(
8 | _fetchedSubreddits),
9 | ]);
10 |
11 | BuiltMap _fetchedSubreddits(
12 | BuiltMap state, FetchedSubreddits action) {
13 | return state
14 | .rebuild((b) => action.subreddits.forEach((sub) => b[sub.id] = sub));
15 | }
16 |
--------------------------------------------------------------------------------
/lib/store/subreddits_search/subreddits_search_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:built_value/built_value.dart';
3 |
4 | part 'subreddits_search_state.g.dart';
5 |
6 | abstract class SubredditsSearchState
7 | implements Built {
8 | String get lastQuery;
9 |
10 | BuiltList get resultFeedsIds;
11 |
12 | SubredditsSearchState._();
13 |
14 | factory SubredditsSearchState([updates(SubredditsSearchStateBuilder b)]) {
15 | return _$SubredditsSearchState._(
16 | lastQuery: '',
17 | resultFeedsIds: BuiltList([]),
18 | ).rebuild(updates);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/widgets/nsfw_badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NsfwBadge extends StatelessWidget {
4 | const NsfwBadge({Key key}) : super(key: key);
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | return Container(
9 | margin: const EdgeInsets.only(right: 8.0),
10 | decoration: BoxDecoration(
11 | borderRadius: BorderRadius.circular(4.0),
12 | color: Colors.grey,
13 | ),
14 | padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 4.0),
15 | child: const Text(
16 | '18+',
17 | style: TextStyle(
18 | fontSize: 14.0,
19 | color: Colors.white,
20 | ),
21 | ),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/widgets/subreddit_circle_avatar.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:reddigram/models/models.dart';
4 |
5 | class SubredditCircleAvatar extends StatelessWidget {
6 | final Subreddit subreddit;
7 |
8 | const SubredditCircleAvatar({Key key, @required this.subreddit})
9 | : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return CircleAvatar(
14 | backgroundColor: subreddit.primaryColorMapped ?? Colors.transparent,
15 | backgroundImage: subreddit.iconUrl.isNotEmpty
16 | ? CachedNetworkImageProvider(subreddit.iconUrl)
17 | : null,
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/widgets/preferences_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:reddigram/store/store.dart';
3 |
4 | class PreferencesProvider extends InheritedWidget {
5 | final PreferencesState preferences;
6 | final Widget child;
7 |
8 | PreferencesProvider({@required this.preferences, @required this.child})
9 | : assert(preferences != null),
10 | assert(child != null);
11 |
12 | static PreferencesState of(BuildContext context) {
13 | return (context.inheritFromWidgetOfExactType(PreferencesProvider)
14 | as PreferencesProvider)
15 | .preferences;
16 | }
17 |
18 | @override
19 | bool updateShouldNotify(PreferencesProvider oldWidget) =>
20 | preferences != oldWidget.preferences;
21 | }
22 |
--------------------------------------------------------------------------------
/lib/store/subreddits_search/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:reddigram/store/store.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | Reducer subredditsSearchReducer = combineReducers([
5 | TypedReducer(
6 | _setSubredditsSearchResult),
7 | TypedReducer(_clearSearch),
8 | ]);
9 |
10 | SubredditsSearchState _setSubredditsSearchResult(
11 | SubredditsSearchState state, FetchedSearchSubreddits action) {
12 | return state.rebuild((b) => b
13 | ..lastQuery = action.query
14 | ..resultFeedsIds.replace(action.resultSubredditsIds));
15 | }
16 |
17 | SubredditsSearchState _clearSearch(
18 | SubredditsSearchState state, ClearSearch action) {
19 | return SubredditsSearchState();
20 | }
21 |
--------------------------------------------------------------------------------
/lib/store/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:reddigram/store/store.dart';
2 |
3 | ReddigramState rootReducer(ReddigramState state, action) {
4 | return state.rebuild((b) => b
5 | ..authState.replace(authStateReducer(state.authState, action))
6 | ..preferences.replace(preferencesReducer(state.preferences, action))
7 | ..photos.replace(photosReducer(state.photos, action))
8 | ..feeds.replace(feedsReducer(state.feeds, action))
9 | ..subreddits.replace(subredditsReducer(state.subreddits, action))
10 | ..subscriptions.replace(subscriptionsReducer(state.subscriptions, action))
11 | ..suggestedSubscriptions.replace(
12 | suggestedSubscriptionsReducer(state.suggestedSubscriptions, action))
13 | ..subredditsSearch
14 | .replace(subredditsSearchReducer(state.subredditsSearch, action)));
15 | }
16 |
--------------------------------------------------------------------------------
/lib/api/mappers/subreddit_info_mapper.dart:
--------------------------------------------------------------------------------
1 | import 'package:reddigram/api/response_models/response_models.dart';
2 | import 'package:reddigram/models/models.dart' as models;
3 |
4 | class SubredditInfoMapper {
5 | static models.Subreddit map(SubredditResponse response) {
6 | return models.Subreddit((b) => b
7 | ..id = response.data.name
8 | ..name = response.data.displayName
9 | ..nsfw = response.data.nsfw ?? false
10 | ..primaryColor = response.data.primaryColor ?? ''
11 | ..iconUrl = response.data.iconUrl ?? ''
12 | ..submissionType = response.data.submissionType ?? '');
13 | }
14 |
15 | static List mapList(SubredditListResponse response) {
16 | return response.data.children
17 | .skipWhile((feed) => feed.data.subredditType == 'private')
18 | .map(map)
19 | .toList();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_crashlytics/firebase_crashlytics.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:reddigram/app.dart';
4 | import 'package:reddigram/store/store.dart';
5 | import 'package:redux/redux.dart';
6 | import 'package:redux_thunk/redux_thunk.dart' show thunkMiddleware;
7 |
8 | void main() {
9 | FlutterError.onError = (details) {
10 | FlutterError.dumpErrorToConsole(details);
11 | Crashlytics.instance.onError(details);
12 | };
13 |
14 | final store = Store(
15 | rootReducer,
16 | initialState: ReddigramState(),
17 | middleware: [
18 | (Store store, action, NextDispatcher next) {
19 | debugPrint(action.toString());
20 |
21 | next(action);
22 | },
23 | thunkMiddleware
24 | ],
25 | );
26 |
27 | runApp(ReddigramApp(store: store));
28 | }
29 |
--------------------------------------------------------------------------------
/lib/store/store.dart:
--------------------------------------------------------------------------------
1 | export 'auth/actions.dart';
2 | export 'auth/auth_state.dart';
3 | export 'auth/reducer.dart';
4 |
5 | export 'feeds/actions.dart';
6 | export 'feeds/reducer.dart';
7 |
8 | export 'photos/actions.dart';
9 | export 'photos/reducer.dart';
10 |
11 | export 'preferences/actions.dart';
12 | export 'preferences/preferences_state.dart';
13 | export 'preferences/reducer.dart';
14 |
15 | export 'subreddits/actions.dart';
16 | export 'subreddits/reducer.dart';
17 |
18 | export 'subreddits_search/subreddits_search_state.dart';
19 | export 'subreddits_search/reducer.dart';
20 | export 'subreddits_search/actions.dart';
21 |
22 | export 'subscriptions/actions.dart';
23 | export 'subscriptions/reducer.dart';
24 |
25 | export 'suggested_subscriptions/actions.dart';
26 | export 'suggested_subscriptions/reducer.dart';
27 |
28 | export 'app_state.dart';
29 | export 'reducer.dart';
30 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.31'
3 | repositories {
4 | google()
5 | jcenter()
6 | maven { url 'https://maven.fabric.io/public' }
7 | }
8 |
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.3.2'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath 'com.google.gms:google-services:3.2.1'
13 | classpath 'io.fabric.tools:gradle:1.26.1'
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | rootProject.buildDir = '../build'
25 | subprojects {
26 | project.buildDir = "${rootProject.buildDir}/${project.name}"
27 | }
28 | subprojects {
29 | project.evaluationDependsOn(':app')
30 | }
31 |
32 | task clean(type: Delete) {
33 | delete rootProject.buildDir
34 | }
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/store/feeds/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:reddigram/models/models.dart';
3 | import 'package:reddigram/store/store.dart';
4 | import 'package:redux/redux.dart';
5 |
6 | Reducer> feedsReducer = combineReducers([
7 | TypedReducer, FetchedFreshFeed>(_fetchedFreshFeed),
8 | TypedReducer, FetchedMoreFeed>(_fetchedMoreFeed),
9 | ]);
10 |
11 | BuiltMap _fetchedFreshFeed(
12 | BuiltMap state, FetchedFreshFeed action) {
13 | return state.rebuild((b) => b[action.name] = action.feed);
14 | }
15 |
16 | BuiltMap _fetchedMoreFeed(
17 | BuiltMap state, FetchedMoreFeed action) {
18 | return state.rebuild((b) => b.updateValue(action.name,
19 | (b) => b.rebuild((b) => b.photosIds.addAll(action.photosIds))));
20 | }
21 |
--------------------------------------------------------------------------------
/lib/store/suggested_subscriptions/actions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:reddigram/api/api.dart';
4 | import 'package:reddigram/store/store.dart';
5 | import 'package:redux/redux.dart';
6 | import 'package:redux_thunk/redux_thunk.dart';
7 |
8 | ThunkAction fetchSuggestedSubscriptions([Completer completer]) {
9 | return (Store store) {
10 | apiRepository
11 | .suggestedSubreddits(store.state.subscriptions.toList())
12 | .then((suggestions) {
13 | store.dispatch(FetchedSuggestedSubscriptions(suggestions));
14 | store.dispatch(fetchSubreddits(suggestions));
15 | })
16 | .whenComplete(() => completer?.complete());
17 | };
18 | }
19 |
20 | class FetchedSuggestedSubscriptions {
21 | final List suggestedSubscriptions;
22 |
23 | FetchedSuggestedSubscriptions(this.suggestedSubscriptions);
24 | }
25 |
--------------------------------------------------------------------------------
/lib/widgets/state_aware/show_nsfw_preference_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_redux/flutter_redux.dart';
3 | import 'package:reddigram/store/store.dart';
4 | import 'package:reddigram/widgets/widgets.dart';
5 |
6 | class ShowNsfwPreferenceTile extends StatelessWidget {
7 | const ShowNsfwPreferenceTile({Key key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return StoreConnector(
12 | converter: (store) => PreferenceViewModel(
13 | value: store.state.preferences.showNsfw,
14 | onSwitch: (showNsfw) => store.dispatch(setShowNsfw(showNsfw)),
15 | ),
16 | builder: (context, vm) => SwitchListTile(
17 | title: const Text('Show adult content'),
18 | secondary: const Icon(Icons.block),
19 | value: vm.value,
20 | onChanged: vm.onSwitch,
21 | ),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/store/preferences/preferences_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:built_value/built_value.dart';
3 |
4 | part 'preferences_state.g.dart';
5 |
6 | abstract class PreferencesState
7 | implements Built {
8 | AppTheme get theme;
9 |
10 | bool get showNsfw;
11 |
12 | bool get cutLongPhotos;
13 |
14 | PreferencesState._();
15 |
16 | factory PreferencesState([updates(PreferencesStateBuilder b)]) {
17 | return _$PreferencesState._(
18 | theme: AppTheme.light,
19 | showNsfw: false,
20 | cutLongPhotos: false,
21 | ).rebuild(updates);
22 | }
23 | }
24 |
25 | class AppTheme extends EnumClass {
26 | static const AppTheme light = _$light;
27 | static const AppTheme dark = _$dark;
28 |
29 | const AppTheme._(String name) : super(name);
30 |
31 | static BuiltSet get values => _$values;
32 |
33 | static AppTheme valueOf(String name) => _$valueOf(name);
34 | }
35 |
--------------------------------------------------------------------------------
/lib/api/api_repository/local.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | class ApiLocalRepository {
4 | static const preferencesKey = "local_subscriptions";
5 |
6 | Future> fetchSubscriptions() {
7 | return SharedPreferences.getInstance()
8 | .then((prefs) => prefs.getStringList(preferencesKey) ?? []);
9 | }
10 |
11 | Future subscribeSubreddit(String name) {
12 | return SharedPreferences.getInstance().then((prefs) {
13 | final subs = prefs.getStringList(preferencesKey) ?? [];
14 |
15 | if (!subs.contains(name)) {
16 | subs.add(name);
17 | prefs.setStringList(preferencesKey, subs);
18 | }
19 | });
20 | }
21 |
22 | Future unsubscribeSubreddit(String name) {
23 | return SharedPreferences.getInstance().then((prefs) {
24 | final subs = prefs.getStringList(preferencesKey) ?? [];
25 |
26 | subs.removeWhere((sub) => sub == name);
27 | prefs.setStringList(preferencesKey, subs);
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/widgets/state_aware/dark_theme_preference_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_redux/flutter_redux.dart';
3 | import 'package:reddigram/store/store.dart';
4 | import 'package:reddigram/widgets/widgets.dart';
5 |
6 | class DarkThemePreferenceTile extends StatelessWidget {
7 | const DarkThemePreferenceTile({Key key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return StoreConnector(
12 | converter: (store) => PreferenceViewModel(
13 | value: store.state.preferences.theme,
14 | onSwitch: (dark) =>
15 | store.dispatch(setTheme(dark ? AppTheme.dark : AppTheme.light)),
16 | ),
17 | builder: (context, vm) => SwitchListTile(
18 | title: const Text('Dark theme'),
19 | secondary: const Icon(Icons.invert_colors),
20 | value: vm.value == AppTheme.dark,
21 | onChanged: vm.onSwitch,
22 | ),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/store/photos/actions.dart:
--------------------------------------------------------------------------------
1 | import 'package:reddigram/api/api.dart';
2 | import 'package:reddigram/models/models.dart';
3 | import 'package:reddigram/store/store.dart';
4 | import 'package:redux/redux.dart';
5 | import 'package:redux_thunk/redux_thunk.dart';
6 |
7 | ThunkAction upvote(Photo photo) {
8 | return (Store store) {
9 | redditRepository
10 | .upvote(photo.id)
11 | .then((_) => store.dispatch(PhotoUpvoted(photo.id)));
12 | };
13 | }
14 |
15 | ThunkAction cancelUpvote(Photo photo) {
16 | return (Store store) {
17 | redditRepository
18 | .cancelUpvote(photo.id)
19 | .then((_) => store.dispatch(PhotoUpvoteCanceled(photo.id)));
20 | };
21 | }
22 |
23 | class FetchedPhotos {
24 | final List photos;
25 |
26 | FetchedPhotos(this.photos);
27 | }
28 |
29 | class PhotoUpvoted {
30 | final String id;
31 |
32 | PhotoUpvoted(this.id);
33 | }
34 |
35 | class PhotoUpvoteCanceled {
36 | final String id;
37 |
38 | PhotoUpvoteCanceled(this.id);
39 | }
40 |
--------------------------------------------------------------------------------
/lib/models/subreddit.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:built_value/built_value.dart';
4 | import 'package:built_value/serializer.dart';
5 |
6 | part 'subreddit.g.dart';
7 |
8 | abstract class Subreddit implements Built {
9 | String get id;
10 |
11 | String get name;
12 |
13 | bool get nsfw;
14 |
15 | String get primaryColor;
16 |
17 | Color get primaryColorMapped {
18 | return primaryColor.isNotEmpty
19 | ? Color(int.parse('FF' + primaryColor.replaceAll('#', ''), radix: 16))
20 | : null;
21 | }
22 |
23 | String get iconUrl;
24 |
25 | String get submissionType;
26 |
27 | Subreddit._();
28 |
29 | factory Subreddit([updates(SubredditBuilder b)]) {
30 | return _$Subreddit
31 | ._(
32 | id: '',
33 | name: '',
34 | nsfw: false,
35 | primaryColor: '',
36 | iconUrl: '',
37 | submissionType: '',
38 | )
39 | .rebuild(updates);
40 | }
41 |
42 | static Serializer get serializer => _$subredditSerializer;
43 | }
44 |
--------------------------------------------------------------------------------
/lib/store/auth/auth_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:built_value/built_value.dart';
3 |
4 | part 'auth_state.g.dart';
5 |
6 | abstract class AuthState implements Built {
7 | @nullable
8 | String get username;
9 |
10 | AuthStatus get status;
11 |
12 | AuthState._();
13 |
14 | factory AuthState([updates(AuthStateBuilder b)]) {
15 | return _$AuthState._(
16 | username: null,
17 | status: AuthStatus.unknown,
18 | ).rebuild(updates);
19 | }
20 | }
21 |
22 | class AuthStatus extends EnumClass {
23 | static const AuthStatus unknown = _$unknown;
24 | static const AuthStatus guest = _$guest;
25 | static const AuthStatus authenticating = _$authenticating;
26 | static const AuthStatus authenticated = _$authenticated;
27 | static const AuthStatus signingOut = _$signingOut;
28 |
29 | const AuthStatus._(String name) : super(name);
30 |
31 | static BuiltSet get values => _$values;
32 |
33 | static AuthStatus valueOf(String name) => _$valueOf(name);
34 | }
35 |
--------------------------------------------------------------------------------
/lib/store/subscriptions/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_collection/built_collection.dart';
2 | import 'package:reddigram/store/store.dart';
3 | import 'package:redux/redux.dart';
4 |
5 | Reducer> subscriptionsReducer = combineReducers([
6 | TypedReducer, FetchedSubscriptions>(_fetchedSubscriptions),
7 | TypedReducer, SubscribedSubreddit>(_subscribedSubreddit),
8 | TypedReducer, UnsubscribedSubreddit>(_unsubscribedSubreddit),
9 | ]);
10 |
11 | BuiltSet _fetchedSubscriptions(
12 | BuiltSet state, FetchedSubscriptions action) {
13 | return state.rebuild((b) => b.replace(action.subreddits));
14 | }
15 |
16 | BuiltSet _subscribedSubreddit(
17 | BuiltSet state, SubscribedSubreddit action) {
18 | return state.rebuild((b) => b.add(action.name));
19 | }
20 |
21 | BuiltSet _unsubscribedSubreddit(
22 | BuiltSet state, UnsubscribedSubreddit action) {
23 | return state.rebuild((b) =>
24 | b.removeWhere((sub) => sub.toLowerCase() == action.name.toLowerCase()));
25 | }
26 |
--------------------------------------------------------------------------------
/lib/store/preferences/reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:reddigram/store/store.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | Reducer preferencesReducer = combineReducers([
5 | TypedReducer(_setPreferencesBulk),
6 | TypedReducer(_setTheme),
7 | TypedReducer(_setShowNsfw),
8 | TypedReducer(_setCutLongPhotos),
9 | ]);
10 |
11 | PreferencesState _setPreferencesBulk(
12 | PreferencesState state, SetPreferencesBulk action) {
13 | return action.preferences;
14 | }
15 |
16 | PreferencesState _setTheme(PreferencesState state, SetTheme action) {
17 | return state.rebuild((b) => b..theme = action.theme);
18 | }
19 |
20 | PreferencesState _setShowNsfw(PreferencesState state, SetShowNsfw action) {
21 | return state.rebuild((b) => b..showNsfw = action.showNsfw);
22 | }
23 |
24 | PreferencesState _setCutLongPhotos(
25 | PreferencesState state, SetCutLongPhotos action) {
26 | return state.rebuild((b) => b..cutLongPhotos = action.cutLongPhotos);
27 | }
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ## About the app
9 |
10 | 
11 |
12 | Glance is an unofficial client for Reddit. However, it allows you to only browse through media posts such as photos and videos. It's purpose is mainly to lurk majestic photos subreddits as well as memes, videos and gifs.
13 |
14 | ### Features
15 |
16 | - Subscribing to subs (separately from your Reddit account)
17 | - Importing subscriptions from your Reddit account
18 | - Upvoting (click the arrow or double-tap)
19 | - Downloading photos
20 | - Browsing only certain subreddit
21 | - Dark mode
22 |
23 | ## Related websites
24 |
25 | - [Landing page](https://reddigram.wolszon.me)
26 | - [r/GlanceApp](https://www.reddit.com/r/GlanceApp/)
27 |
28 | ## Related repositories
29 |
30 | - [Glance API repository](https://github.com/Albert221/GlanceApi)
31 | - [Landing page repository](https://github.com/Albert221/GlanceLanding)
32 |
33 |
--------------------------------------------------------------------------------
/android/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "564944230037",
4 | "firebase_url": "https://reddigram-a9b2a.firebaseio.com",
5 | "project_id": "reddigram-a9b2a",
6 | "storage_bucket": "reddigram-a9b2a.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:564944230037:android:8ee4e4deed841a5e",
12 | "android_client_info": {
13 | "package_name": "me.wolszon.reddigram"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "564944230037-lq9kksm95knu219m526a7fk6mte91pgi.apps.googleusercontent.com",
19 | "client_type": 3
20 | }
21 | ],
22 | "api_key": [
23 | {
24 | "current_key": "AIzaSyAM_tlWshli1nv58I4m-vUYduH3oLrP7DM"
25 | }
26 | ],
27 | "services": {
28 | "appinvite_service": {
29 | "other_platform_oauth_client": [
30 | {
31 | "client_id": "564944230037-lq9kksm95knu219m526a7fk6mte91pgi.apps.googleusercontent.com",
32 | "client_type": 3
33 | }
34 | ]
35 | }
36 | }
37 | }
38 | ],
39 | "configuration_version": "1"
40 | }
--------------------------------------------------------------------------------
/lib/models/photo.dart:
--------------------------------------------------------------------------------
1 | import 'package:built_value/built_value.dart';
2 |
3 | part 'photo.g.dart';
4 |
5 | abstract class Photo implements Built {
6 | String get id;
7 |
8 | String get title;
9 |
10 | String get authorName;
11 |
12 | String get subredditName;
13 |
14 | String get subredditId;
15 |
16 | PhotoMedia get source;
17 |
18 | PhotoMedia get fullImage;
19 |
20 | PhotoMedia get thumbnail;
21 |
22 | @nullable
23 | Video get video;
24 |
25 | bool get isVideo => video != null;
26 |
27 | int get upvotes;
28 |
29 | bool get upvoted;
30 |
31 | bool get nsfw;
32 |
33 | String get redditUrl;
34 |
35 | Photo._();
36 |
37 | factory Photo([updates(PhotoBuilder b)]) = _$Photo;
38 | }
39 |
40 | abstract class PhotoMedia implements Built {
41 | String get url;
42 |
43 | int get width;
44 |
45 | int get height;
46 |
47 | double get aspectRatio => width / height;
48 |
49 | PhotoMedia._();
50 |
51 | factory PhotoMedia([updates(PhotoMediaBuilder b)]) = _$PhotoMedia;
52 | }
53 |
54 | abstract class Video implements Built