├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
└── PULL_REQUEST_TEMPLATE
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── org
│ │ └── loklak
│ │ └── wok
│ │ └── SuggestPresenterTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── org
│ │ │ └── loklak
│ │ │ └── wok
│ │ │ ├── Constants.java
│ │ │ ├── LoklakWokApplication.java
│ │ │ ├── Utility.java
│ │ │ ├── adapters
│ │ │ ├── HarvestedTweetAdapter.java
│ │ │ ├── PostTweetMediaAdapter.java
│ │ │ ├── SearchCategoryAdapter.java
│ │ │ ├── SearchFragmentPagerAdapter.java
│ │ │ ├── SuggestAdapter.java
│ │ │ └── TweetImagesAdapter.java
│ │ │ ├── api
│ │ │ ├── loklak
│ │ │ │ ├── LoklakAPI.java
│ │ │ │ └── RestClient.java
│ │ │ └── twitter
│ │ │ │ ├── TwitterAPI.java
│ │ │ │ ├── TwitterMediaAPI.java
│ │ │ │ ├── TwitterMediaRestClient.java
│ │ │ │ ├── TwitterOAuthInterceptor.java
│ │ │ │ ├── TwitterRestClient.java
│ │ │ │ └── UrlEscapeUtils.java
│ │ │ ├── inject
│ │ │ ├── ApplicationComponent.java
│ │ │ └── ApplicationModule.java
│ │ │ ├── model
│ │ │ ├── harvest
│ │ │ │ ├── Push.java
│ │ │ │ ├── ScrapedData.java
│ │ │ │ ├── Status.java
│ │ │ │ └── User.java
│ │ │ ├── search
│ │ │ │ ├── Search.java
│ │ │ │ ├── SearchMetadata.java
│ │ │ │ ├── Status.java
│ │ │ │ └── User.java
│ │ │ ├── suggest
│ │ │ │ ├── Query.java
│ │ │ │ ├── SearchMetadata.java
│ │ │ │ └── SuggestData.java
│ │ │ └── twitter
│ │ │ │ ├── AccountVerifyCredentials.java
│ │ │ │ ├── MediaEntity.java
│ │ │ │ ├── MediaUpload.java
│ │ │ │ ├── StatusEntities.java
│ │ │ │ └── StatusUpdate.java
│ │ │ ├── tools
│ │ │ └── LogLines.java
│ │ │ ├── ui
│ │ │ ├── activity
│ │ │ │ ├── SearchActivity.java
│ │ │ │ ├── SplashActivity.java
│ │ │ │ ├── TweetHarvestingActivity.java
│ │ │ │ └── TweetPostingActivity.java
│ │ │ ├── fragment
│ │ │ │ ├── SearchCategoryFragment.java
│ │ │ │ ├── SearchFragment.java
│ │ │ │ ├── TweetHarvestingFragment.java
│ │ │ │ └── TweetPostingFragment.java
│ │ │ └── suggestion
│ │ │ │ ├── SuggestActivity.java
│ │ │ │ ├── SuggestContract.java
│ │ │ │ ├── SuggestFragment.java
│ │ │ │ └── SuggestPresenter.java
│ │ │ └── utility
│ │ │ ├── Constants.java
│ │ │ ├── FileUtils.java
│ │ │ └── SharedPrefUtil.java
│ └── res
│ │ ├── anim
│ │ ├── back_button_bottom_exit.xml
│ │ ├── back_button_enter.xml
│ │ ├── back_button_exit.xml
│ │ ├── back_button_top_enter.xml
│ │ ├── bottom_enter.xml
│ │ ├── enter.xml
│ │ ├── exit.xml
│ │ └── top_exit.xml
│ │ ├── drawable
│ │ ├── camera.xml
│ │ ├── heart.xml
│ │ ├── ic_add_location_24dp.xml
│ │ ├── ic_arrow_back_24dp.xml
│ │ ├── ic_close_24dp.xml
│ │ ├── ic_close_black_24dp.xml
│ │ ├── ic_close_white.xml
│ │ ├── ic_gallery_24dp.xml
│ │ ├── ic_location_on_24dp.xml
│ │ ├── ic_search_24dp.xml
│ │ ├── loklak_splash.png
│ │ ├── media_button_selector.xml
│ │ ├── reply.xml
│ │ ├── retweet.xml
│ │ ├── shape_round_corner_tweet_media_remove.xml
│ │ ├── shape_round_corner_tweet_post.xml
│ │ ├── tweet_button_selector.xml
│ │ ├── twitter_pink.xml
│ │ └── twitter_white.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_search.xml
│ │ ├── activity_splash.xml
│ │ ├── activity_suggest.xml
│ │ ├── activity_tweet_harvesting.xml
│ │ ├── activity_tweet_posting.xml
│ │ ├── content_tweet_harvesting.xml
│ │ ├── fab_tweet_posting.xml
│ │ ├── fragment_search.xml
│ │ ├── fragment_search_category.xml
│ │ ├── fragment_suggest.xml
│ │ ├── fragment_tweet_harvesting.xml
│ │ ├── fragment_tweet_posting.xml
│ │ ├── grid_elem_tweet_photo.xml
│ │ ├── item_post_tweet_image.xml
│ │ ├── row_harvested_tweet.xml
│ │ ├── row_tweet_search.xml
│ │ └── row_tweet_search_suggest.xml
│ │ ├── menu
│ │ ├── menu_tweet_harvesting.xml
│ │ └── menu_tweet_posting.xml
│ │ ├── mipmap-hdpi
│ │ └── launcher.png
│ │ ├── mipmap-mdpi
│ │ └── launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── launcher.png
│ │ ├── raw
│ │ └── twitter.js
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── org
│ └── loklak
│ └── wok
│ ├── TwitterOAuthInterceptorTest.java
│ └── UrlEscapeUtilsTest.java
├── build.gradle
├── docs
└── _static
│ ├── tweet_harvesting.png
│ ├── tweet_post_authorization.png
│ ├── tweet_posting.png
│ ├── tweet_search.png
│ ├── tweet_search_suggestions.png
│ └── twitter_authorization_login.png
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
├── prep-key.sh
└── secrets.tar.enc
├── settings.gradle
└── update-apk.sh
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = false
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespaces = true
10 |
11 | [{*.java, *.xml}]
12 | indent_style = space
13 | indent_size = 4
14 | insert_final_newline = true
15 |
16 | [*.md]
17 | trim_trailing_whitespaces = false
18 |
19 | [*.json]
20 | indent_style = space
21 | indent_size = 2
22 |
23 | [{*.yml,*.yaml}]
24 | indent_style = space
25 | indent_size = 2
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE:
--------------------------------------------------------------------------------
1 | **Android version and Phone Model**
2 |
3 | Add the Android version and the phone model on which the issue was encountered.
4 |
5 | **Actual Behaviour**
6 |
7 | Please state here what is currently happening.
8 |
9 | **Expected Behaviour**
10 |
11 | State here what the feature should enable the user to do.
12 |
13 | **Steps to reproduce it**
14 |
15 | Add steps to reproduce bugs or add information on the place where the feature should be implemented. Add links to a sample deployment or code.
16 |
17 | **LogCat for the issue**
18 |
19 | Provide logs for the crash here
20 |
21 | **Screenshots of the issue**
22 |
23 | Where-ever possible attach a screenshot of the issue.
24 |
25 | **Would you like to work on the issue?**
26 |
27 | Please let us know if you can work on it or the issue should be assigned to someone else.
28 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 | Fix #[Add issue number here. If you do not solve the issue entirely, please change the message e.g. "First steps for issues #IssueNumber]
2 |
3 | Changes: [Add here what changes were made in this issue and if possible provide links.]
4 |
5 | Screenshots for the change:
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | .DS_Store
4 | /build
5 | /captures
6 | .idea
7 | *.iml
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | sudo: required
4 |
5 | env:
6 | global:
7 | - GRADLE_OPTS="-Xmx512m"
8 | matrix:
9 | - ADB_INSTALL_TIMEOUT=15
10 |
11 | cache:
12 | directories:
13 | - "${TRAVIS_BUILD_DIR}/android/gradle/caches/"
14 | - "${TRAVIS_BUILD_DIR}/android/gradle/wrapper/dists/"
15 | - "$HOME/android/.gradle/caches/"
16 | - "$HOME/android/.gradle/wrapper/"
17 |
18 | android:
19 | components:
20 | - tools
21 | - platform-tools
22 | - build-tools-26.0.2
23 | - tools
24 | - android-25
25 | - android-22
26 | - addon-google_apis-google-25
27 | - extra-google-m2repository
28 | - extra-android-m2repository
29 | - android-sdk-license-.+
30 |
31 | # system images for emulator
32 | - sys-img-armeabi-v7a-android-22
33 |
34 | before_script:
35 | - chmod +x gradlew
36 | - echo no | android create avd --force --name test --target android-22 --abi armeabi-v7a
37 | - emulator -avd test -no-skin -no-audio -no-window &
38 |
39 | script:
40 | - android-wait-for-emulator
41 | - adb shell input keyevent 82 &
42 | - ./gradlew connectedCheck test jacocoTestReport
43 | - ./gradlew build
44 | - ./gradlew assembleRelease
45 | - bash scripts/prep-key.sh
46 | # Success or not if a pull request is merged the apk should be updated if it can be build
47 | - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./update-apk.sh; fi'
48 |
49 | after_success:
50 | - bash <(curl -s https://codecov.io/bash)
51 |
52 | notifications:
53 | webhooks:
54 |
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Loklak Wok for Android
2 |
3 | [](https://travis-ci.org/fossasia/loklak_wok_android)
4 | [](https://codecov.io/github/fossasia/loklak_wok_android)
5 |
6 | Loklak Wok Android is a message harvesting peer for the loklak_server.
7 |
8 | Users can also search tweets from the app, the displayed tweets are the latest tweets, tweets containing images and videos. Along with that, the app provides a tweet posting feature. Users can directly post the tweet from the app. Not only text, but images can also be tweeted from the app.
9 |
10 | ### Screenshots of the app
11 |
12 |
13 |
14 |  |
15 |  |
16 |  |
17 |
18 |  |
19 |  |
20 |  |
21 |
22 |
23 |
24 | ## Communication
25 | Please join our mailing list to discuss questions regarding the project: https://groups.google.com/forum/#!forum/opntec-dev
26 |
27 | Our chat channel is on gitter here: https://gitter.im/loklak/loklak.
28 |
29 | ## Development Environment Setup
30 | Clone the repository
31 | ```
32 | $ git clone https://github.com/fossasia/loklak_wok_android.git
33 | ```
34 | Before you begin, you should already have the Android Studio SDK downloaded and set up correctly. You can find a guide on how to do this here: [Setting up Android Studio](http://developer.android.com/sdk/installing/index.html?pkg=studio)
35 |
36 | ### Setting up the Android Project
37 |
38 | 1. Download the [_loklak_wok_android_ project source](https://github.com/fossasia/loklak_wok_android). You can do this either by forking and cloning the repository (recommended if you plan on pushing changes) or by downloading it as a ZIP file and extracting it.
39 |
40 | 2. Open Android Studio, you will see a **Welcome to Android** window. Under Quick Start, select _Import Project (Eclipse ADT, Gradle, etc.)_
41 |
42 | 3. Navigate to the directory where you saved the loklak_wok_android project, select this folder.
43 |
44 | 4. Once this process is complete and Android Studio opens, check the Console for any build errors.
45 | _Note:_ If you receive a Gradle sync error titled, "failed to find ...", you should click on the link below the error message (if available) that says _Install missing platform(s) and sync project_ and allow Android studio to fetch you what is missing.
46 |
47 | 5. Once all build errors have been resolved, you should be all set to build the app and test it.
48 |
49 | 6. To Build the app, go to _Build>Make Project_ (or alternatively press the Make Project icon in the toolbar).
50 |
51 | 7. If the app was built successfully, you can test it by running it on either a real device or an emulated one by going to _Run>Run 'app'_ or pressing the Run icon in the toolbar.
52 |
53 | ## Libraries Used
54 | * Retrofit [docs](http://square.github.io/retrofit/2.x/retrofit/)
55 | * Gson [docs](http://www.javadoc.io/doc/com.google.code.gson/gson/2.8.1)
56 | * ButterKnife [docs](http://jakewharton.github.io/butterknife/javadoc/)
57 | * RxJava [docs](http://reactivex.io/RxJava/javadoc/)
58 | * RxAndroid [docs](https://www.javadoc.io/doc/io.reactivex/rxandroid/1.2.1)
59 | * LiquidCore [docs](https://liquidplayer.github.io/LiquidCoreAndroid/0.2.2/)
60 | * Glide [docs](http://bumptech.github.io/glide/javadocs/images/360/index.html)
61 |
62 | The project uses **lambda expressions** and **RxJava** heavily. So, if you are new to these, the
63 | following resources would be helpful to get you started:
64 | * Lambda Expressions
65 | * [Jenkov Tutorials](http://tutorials.jenkov.com/java/lambda-expressions.html) for a quick start.
66 | * [Java Brains](https://www.youtube.com/playlist?list=PLqq-6Pq4lTTa9YGfyhyW2CqdtW9RtY-I3) youtube
67 | series for a detail understanding.
68 | * RxJava
69 | * [Grokking RxJava](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/) series by Dan Lew.
70 | * Code Tutplus RxJava series ([Introduction](https://code.tutsplus.com/tutorials/getting-started-with-rxjava-20-for-android--cms-28345),
71 | [Operators](https://code.tutsplus.com/tutorials/reactive-programming-operators-in-rxjava-20--cms-28396)
72 | and [Use in Android](https://code.tutsplus.com/tutorials/rxjava-for-android-apps-introducing-rxbinding-and-rxlifecycle--cms-28565)) by Jessica Thornsby.
73 |
74 | ## Contributions, Bug Reports and Feature Requests
75 | This is an Open Source project and we would be happy to see contributors who report bugs and file
76 | feature requests submitting pull requests as well. Please report issues here
77 | https://github.com/fossasia/loklak_wok_android/issues.
78 |
79 | ## Branch Policy
80 | * **master** All development goes on in this branch. If you're making a contribution, you are
81 | supposed to make a pull request to master. PRs to master must pass a build check on Travis.
82 | * **apk** This branch contains apks, that are automatically generated on merging the latest pull request.
83 |
84 | ## Code Style Guidelines
85 | For contributions please read the [CODESTYLE](https://source.android.com/source/code-style) carefully.
86 | An additional rule: Maximum 100 characters per line.
87 |
88 | Try to remove as many warnings (yellow markings on the right side of Android Studio) as possible,
89 | It's not completely possible to remove all the warnings, but over a period of time, we should try to
90 | make it as complete as possible.
91 |
92 | ## LICENSE
93 | This is licensed under LGPL 2.1.
94 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | // TODO: Uncomment to enable crashlytics
3 | // apply plugin: 'io.fabric'
4 | apply plugin: 'realm-android'
5 | apply plugin: 'jacoco-android'
6 |
7 | repositories {
8 | maven { url 'https://maven.fabric.io/public' }
9 | }
10 |
11 | android {
12 | compileSdkVersion 25
13 | buildToolsVersion "26.0.2"
14 |
15 | defaultConfig {
16 | applicationId "org.loklak.wok"
17 | minSdkVersion 18
18 | targetSdkVersion 25
19 | versionCode 1
20 | versionName "1"
21 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
22 | vectorDrawables.useSupportLibrary = true
23 | }
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 |
31 | // This is important, it will run lint checks but won't abort build
32 | lintOptions {
33 | abortOnError false
34 | }
35 |
36 | compileOptions {
37 | sourceCompatibility JavaVersion.VERSION_1_8
38 | targetCompatibility JavaVersion.VERSION_1_8
39 | }
40 |
41 | packagingOptions {
42 | exclude 'META-INF/rxjava.properties'
43 | }
44 | }
45 |
46 | dependencies {
47 | implementation fileTree(include: ['*.jar'], dir: 'libs')
48 |
49 | testImplementation 'junit:junit:4.12'
50 | testImplementation 'org.assertj:assertj-core:1.7.1'
51 | testImplementation 'org.mockito:mockito-core:2.8.47'
52 | testImplementation 'com.android.support:support-annotations:25.3.1'
53 |
54 | androidTestImplementation 'org.mockito:mockito-android:2.8.47'
55 | androidTestImplementation 'com.android.support:support-annotations:25.3.1'
56 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
57 |
58 | implementation 'com.android.support:appcompat-v7:25.3.1'
59 | implementation 'com.android.support:support-v4:25.3.1'
60 | implementation 'com.android.support:design:25.3.1'
61 | implementation 'com.android.support:cardview-v7:25.3.1'
62 |
63 | implementation 'org.joda:joda-convert:1.8'
64 | implementation 'joda-time:joda-time:2.9.4'
65 |
66 | implementation 'com.google.code.gson:gson:2.8.1'
67 |
68 | implementation 'com.squareup.retrofit2:retrofit:2.3.0'
69 | implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
70 | implementation 'com.squareup.okhttp3:logging-interceptor:3.7.0'
71 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
72 |
73 | implementation 'com.jakewharton:butterknife:8.6.0'
74 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
75 |
76 | implementation 'com.google.dagger:dagger:2.11'
77 | annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
78 |
79 | implementation 'io.reactivex.rxjava2:rxjava:2.0.5'
80 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
81 | implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
82 |
83 | implementation 'com.github.LiquidPlayer:LiquidCore:0.2.2'
84 |
85 | implementation 'com.github.bumptech.glide:glide:3.7.0'
86 |
87 | implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
88 | transitive = true;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/admin/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/org/loklak/wok/SuggestPresenterTest.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok;
2 |
3 |
4 | import android.support.test.InstrumentationRegistry;
5 |
6 | import org.junit.After;
7 | import org.junit.AfterClass;
8 | import org.junit.Before;
9 | import org.junit.BeforeClass;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.loklak.wok.api.loklak.LoklakAPI;
13 | import org.loklak.wok.model.suggest.Query;
14 | import org.loklak.wok.model.suggest.SuggestData;
15 | import org.loklak.wok.ui.suggestion.SuggestContract;
16 | import org.loklak.wok.ui.suggestion.SuggestPresenter;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | import java.io.IOException;
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import io.reactivex.Observable;
24 | import io.reactivex.android.plugins.RxAndroidPlugins;
25 | import io.reactivex.plugins.RxJavaPlugins;
26 | import io.reactivex.schedulers.Schedulers;
27 | import io.realm.Realm;
28 | import io.realm.RealmConfiguration;
29 |
30 | import static org.junit.Assert.assertEquals;
31 | import static org.mockito.ArgumentMatchers.anyString;
32 | import static org.mockito.Mockito.mock;
33 | import static org.mockito.Mockito.verify;
34 | import static org.mockito.Mockito.when;
35 |
36 | @RunWith(MockitoJUnitRunner.class)
37 | public class SuggestPresenterTest {
38 |
39 | private SuggestContract.View mMockView;
40 | private SuggestPresenter mPresenter;
41 | private LoklakAPI mApi;
42 | private List queries;
43 | private static Realm mDb;
44 |
45 | @BeforeClass
46 | public static void setDb() {
47 | Realm.init(InstrumentationRegistry.getContext());
48 | RealmConfiguration testConfig = new RealmConfiguration.Builder()
49 | .inMemory()
50 | .name("test-db")
51 | .build();
52 | mDb = Realm.getInstance(testConfig);
53 | }
54 |
55 | @AfterClass
56 | public static void closeDb() {
57 | mDb.close();
58 | }
59 |
60 | @Before
61 | public void setUp() throws Exception {
62 | mMockView = mock(SuggestContract.View.class);
63 | mApi = mock(LoklakAPI.class);
64 |
65 | mPresenter = new SuggestPresenter(mApi, mDb);
66 | mPresenter.attachView(mMockView);
67 |
68 | queries = getFakeQueries();
69 |
70 | RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
71 | RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
72 |
73 | mDb.beginTransaction();
74 | mDb.copyToRealm(queries);
75 | mDb.commitTransaction();
76 | }
77 |
78 | @After
79 | public void tearDown() throws Exception {
80 | mPresenter.detachView();
81 | }
82 |
83 | private List getFakeQueries() {
84 | List queryList = new ArrayList<>();
85 |
86 | Query linux = new Query();
87 | linux.setQuery("linux");
88 | queryList.add(linux);
89 |
90 | Query india = new Query();
91 | india.setQuery("india");
92 | queryList.add(india);
93 |
94 | return queryList;
95 | }
96 |
97 | private Observable getFakeSuggestions() {
98 | SuggestData suggestData = new SuggestData();
99 | suggestData.setQueries(queries);
100 | return Observable.just(suggestData);
101 | }
102 |
103 | private void stubSuggestionsFromApi(Observable observable) {
104 | when(mApi.getSuggestions(anyString())).thenReturn(observable);
105 | }
106 |
107 | @Test
108 | public void testLoadSuggestionsFromApi() {
109 | stubSuggestionsFromApi(getFakeSuggestions());
110 |
111 | mPresenter.loadSuggestionsFromAPI("", true);
112 |
113 | verify(mMockView).showProgressBar(true);
114 | verify(mMockView).onSuggestionFetchSuccessful(queries);
115 | verify(mMockView).showProgressBar(false);
116 | }
117 |
118 | @Test
119 | public void testLoadSuggestionsFromApiFail() {
120 | Throwable throwable = new IOException();
121 | stubSuggestionsFromApi(Observable.error(throwable));
122 |
123 | mPresenter.loadSuggestionsFromAPI("", true);
124 | verify(mMockView).showProgressBar(true);
125 | verify(mMockView).showProgressBar(false);
126 | verify(mMockView).onSuggestionFetchError(throwable);
127 | }
128 |
129 | @Test
130 | public void testSaveSuggestions() {
131 | mPresenter.saveSuggestions(queries);
132 | int count = mDb.where(Query.class).findAll().size();
133 | assertEquals(queries.size(), count);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
28 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
49 |
50 |
53 |
54 |
58 |
59 |
61 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/Constants.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok;
2 |
3 |
4 | public class Constants {
5 |
6 | public static final String TWEET_SEARCH_SUGGESTION_QUERY_KEY = "search_suggestion_query";
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/LoklakWokApplication.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok;
2 |
3 | import android.app.Application;
4 |
5 | import org.loklak.wok.inject.ApplicationComponent;
6 | import org.loklak.wok.inject.DaggerApplicationComponent;
7 | import org.loklak.wok.inject.ApplicationModule;
8 | import org.loklak.wok.utility.Constants;
9 |
10 | import io.realm.Realm;
11 | import io.realm.RealmConfiguration;
12 |
13 |
14 | public class LoklakWokApplication extends Application {
15 |
16 | private ApplicationComponent mApplicationComponent;
17 |
18 | @Override
19 | public void onCreate() {
20 | super.onCreate();
21 | // TODO: uncomment the following and provide API key in manifest file to enable Crashlytics
22 | // Fabric.with(this, new Crashlytics());
23 |
24 | Realm.init(this);
25 | RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
26 | .name(Realm.DEFAULT_REALM_NAME)
27 | .deleteRealmIfMigrationNeeded()
28 | .build();
29 | Realm.setDefaultConfiguration(realmConfiguration);
30 |
31 | mApplicationComponent = DaggerApplicationComponent.builder()
32 | .applicationModule(new ApplicationModule(Constants.BASE_URL_LOKLAK))
33 | .build();
34 | }
35 |
36 | public ApplicationComponent getApplicationComponent() {
37 | return mApplicationComponent;
38 | }
39 |
40 | @Override
41 | public void onTerminate() {
42 | Realm.getDefaultInstance().close();
43 | super.onTerminate();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/Utility.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok;
2 |
3 |
4 | import android.content.Context;
5 | import android.widget.Toast;
6 |
7 | import com.google.gson.FieldNamingPolicy;
8 | import com.google.gson.Gson;
9 | import com.google.gson.GsonBuilder;
10 |
11 | public class Utility {
12 |
13 | public static Gson getGsonForPrivateVariableClass() {
14 | return new GsonBuilder().setFieldNamingStrategy(field -> {
15 | String name = FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES.translateName(field);
16 | return name.substring(2); // private fields are named as mName i.e m_name
17 | }).create();
18 | }
19 |
20 | public static void displayToast(Toast toast, Context context, String toastMessage) {
21 | if (toast != null) toast.cancel();
22 | toast = Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT);
23 | toast.show();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/adapters/HarvestedTweetAdapter.java:
--------------------------------------------------------------------------------
1 |
2 | package org.loklak.wok.adapters;
3 |
4 | import android.support.v7.widget.RecyclerView;
5 | import android.text.format.DateUtils;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.TextView;
10 |
11 | import org.loklak.wok.model.harvest.Status;
12 | import org.loklak.wok.model.harvest.User;
13 | import org.loklak.wok.R;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | import butterknife.BindView;
19 | import butterknife.ButterKnife;
20 |
21 | public class HarvestedTweetAdapter
22 | extends RecyclerView.Adapter {
23 |
24 | private List mHarvestedTweetList;
25 |
26 | public HarvestedTweetAdapter(List harvestedTweets) {
27 | this.mHarvestedTweetList = harvestedTweets;
28 | }
29 |
30 | @Override
31 | public HarvestedTweetViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
32 | LayoutInflater inflater = LayoutInflater.from(parent.getContext());
33 | View view = inflater.inflate(R.layout.row_harvested_tweet, parent, false);
34 | return new HarvestedTweetViewHolder(view);
35 | }
36 |
37 | @Override
38 | public void onBindViewHolder(HarvestedTweetViewHolder holder, int position) {
39 | holder.bind(mHarvestedTweetList.get(position));
40 | }
41 |
42 | @Override
43 | public int getItemCount() {
44 | return mHarvestedTweetList.size();
45 | }
46 |
47 | public void addHarvestedTweets(List harvestedTweetList) {
48 | int count = mHarvestedTweetList.size();
49 | mHarvestedTweetList.addAll(harvestedTweetList);
50 | notifyItemRangeInserted(getItemCount(), count);
51 | }
52 |
53 | public ArrayList getHarvestedTweetList() {
54 | return (ArrayList) mHarvestedTweetList;
55 | }
56 |
57 | public void clearAdapter() {
58 | mHarvestedTweetList.clear();
59 | notifyDataSetChanged();
60 | }
61 |
62 | class HarvestedTweetViewHolder extends RecyclerView.ViewHolder {
63 |
64 | @BindView(R.id.user_fullname)
65 | TextView userFullname;
66 | @BindView(R.id.username)
67 | TextView username;
68 | @BindView(R.id.tweet_date)
69 | TextView tweetDate;
70 | @BindView(R.id.harvested_tweet_text)
71 | TextView harvestedTweetTextView;
72 |
73 | public HarvestedTweetViewHolder(View itemView) {
74 | super(itemView);
75 | ButterKnife.bind(this, itemView);
76 | }
77 |
78 | void bind(Status harvestedTweet) {
79 | User user = harvestedTweet.getUser();
80 | userFullname.setText(user.getName());
81 | username.setText("@" + user.getScreenName());
82 | tweetDate.setText(getReadableDate(harvestedTweet.getCreatedAt()));
83 | harvestedTweetTextView.setText(harvestedTweet.getText());
84 | }
85 |
86 | private String getReadableDate(Long miliseconds) {
87 |
88 | CharSequence formatted = DateUtils.getRelativeTimeSpanString(miliseconds);
89 | return formatted.toString();
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/adapters/PostTweetMediaAdapter.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.adapters;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageButton;
11 | import android.widget.ImageView;
12 |
13 | import org.loklak.wok.R;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | import butterknife.BindView;
19 | import butterknife.ButterKnife;
20 | import butterknife.OnClick;
21 |
22 |
23 | public class PostTweetMediaAdapter
24 | extends RecyclerView.Adapter {
25 |
26 | private final String LOG_TAG = PostTweetMediaAdapter.class.getName();
27 |
28 | private Context mContext;
29 | private List mImagePathList = new ArrayList<>();
30 |
31 | public PostTweetMediaAdapter(Context context, List imagePaths) {
32 | this.mContext = context;
33 | this.mImagePathList = imagePaths;
34 | }
35 |
36 | @Override
37 | public PostTweetMediaViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
38 | LayoutInflater inflater = LayoutInflater.from(mContext);
39 | View view = inflater.inflate(R.layout.item_post_tweet_image, parent, false);
40 | return new PostTweetMediaViewHolder(view);
41 | }
42 |
43 | @Override
44 | public void onBindViewHolder(PostTweetMediaViewHolder holder, int position) {
45 | holder.bind(mImagePathList.get(position));
46 | }
47 |
48 | @Override
49 | public int getItemCount() {
50 | return mImagePathList.size();
51 | }
52 |
53 | public ArrayList getImagePathList() {
54 | return (ArrayList) mImagePathList;
55 | }
56 |
57 | public void setImagePathList(List imagePaths) {
58 | this.mImagePathList = imagePaths;
59 | notifyDataSetChanged();
60 | }
61 |
62 | public void addImagePath(String imagePath) {
63 | mImagePathList.add(imagePath);
64 | notifyDataSetChanged();
65 | }
66 |
67 | public void clearAdapter() {
68 | mImagePathList.clear();
69 | notifyDataSetChanged();
70 | }
71 |
72 | class PostTweetMediaViewHolder extends RecyclerView.ViewHolder {
73 |
74 | private final String LOG_TAG = PostTweetMediaViewHolder.class.getName();
75 |
76 | @BindView(R.id.tweet_media)
77 | ImageView tweetMedia;
78 | @BindView(R.id.tweet_media_remove)
79 | ImageButton tweetMediaRemove;
80 |
81 | PostTweetMediaViewHolder(View itemView) {
82 | super(itemView);
83 | ButterKnife.bind(this, itemView);
84 | }
85 |
86 | void bind(String imagePath) {
87 | Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
88 | tweetMedia.setImageBitmap(bitmap);
89 | }
90 |
91 | @OnClick(R.id.tweet_media_remove)
92 | public void onClickTweetMediaRemove() {
93 | int position = getAdapterPosition();
94 | mImagePathList.remove(position);
95 | notifyItemRemoved(position);
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/adapters/SearchCategoryAdapter.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.adapters;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.support.v7.widget.StaggeredGridLayoutManager;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import com.bumptech.glide.Glide;
13 |
14 | import org.loklak.wok.model.search.Status;
15 | import org.loklak.wok.model.search.User;
16 | import org.loklak.wok.R;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import butterknife.BindView;
22 | import butterknife.ButterKnife;
23 |
24 | public class SearchCategoryAdapter
25 | extends RecyclerView.Adapter {
26 |
27 | private Context mContext;
28 | private List mStatuses;
29 |
30 | public SearchCategoryAdapter(Context context, List statuses) {
31 | this.mContext = context;
32 | this.mStatuses = statuses;
33 | }
34 |
35 | @Override
36 | public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
37 | View view = LayoutInflater.from(mContext).inflate(R.layout.row_tweet_search, parent, false);
38 | return new SearchViewHolder(view);
39 | }
40 |
41 | @Override
42 | public void onBindViewHolder(SearchViewHolder holder, int position) {
43 | holder.bind(mStatuses.get(position));
44 | }
45 |
46 | @Override
47 | public int getItemCount() {
48 | return mStatuses.size();
49 | }
50 |
51 | public ArrayList getStatuses() {
52 | return (ArrayList) mStatuses;
53 | }
54 |
55 | public void setStatuses(List statuses) {
56 | mStatuses = statuses;
57 | notifyDataSetChanged();
58 | }
59 |
60 | class SearchViewHolder extends RecyclerView.ViewHolder {
61 |
62 | @BindView(R.id.user_profile_pic)
63 | ImageView userProfilePic;
64 | @BindView(R.id.user_fullname)
65 | TextView userFullname;
66 | @BindView(R.id.tweet_date)
67 | TextView tweetDate;
68 | @BindView(R.id.username)
69 | TextView username;
70 | @BindView(R.id.tweet_text)
71 | TextView tweetText;
72 | @BindView(R.id.tweet_photos)
73 | RecyclerView tweetPhotos;
74 | @BindView(R.id.number_retweets)
75 | TextView numberRetweets;
76 | @BindView(R.id.number_likes)
77 | TextView numberLikes;
78 |
79 | SearchViewHolder(View itemView) {
80 | super(itemView);
81 | ButterKnife.bind(this, itemView);
82 | }
83 |
84 | void bind(Status status) {
85 | User user = status.getUser();
86 |
87 | Glide.with(mContext).load(user.getProfileImageUrlHttps()).into(userProfilePic);
88 | userFullname.setText(user.getName());
89 | username.setText(user.getScreenName());
90 | tweetText.setText(status.getText());
91 | numberRetweets.setText(String.valueOf(status.getRetweetCount()));
92 | numberLikes.setText(String.valueOf(status.getFavouritesCount()));
93 |
94 | List imageUrls = filterImages(status.getImages());
95 | int orientation = StaggeredGridLayoutManager.VERTICAL;
96 | StaggeredGridLayoutManager layoutManager;
97 | if (imageUrls.size() > 0) {
98 | tweetPhotos.setVisibility(View.VISIBLE);
99 | if (imageUrls.size() == 1) {
100 | layoutManager = new StaggeredGridLayoutManager(1, orientation);
101 | } else {
102 | layoutManager = new StaggeredGridLayoutManager(2, orientation);
103 | }
104 | layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
105 | tweetPhotos.setLayoutManager(layoutManager);
106 | TweetImagesAdapter tweetImagesAdapter = new TweetImagesAdapter(mContext, imageUrls);
107 | tweetPhotos.setAdapter(tweetImagesAdapter);
108 | } else {
109 | tweetPhotos.setVisibility(View.GONE);
110 | }
111 | }
112 |
113 | private List filterImages(List imageUrls) {
114 | List onlyPbsImages = new ArrayList<>();
115 | for (String url: imageUrls) {
116 | if (url.contains("pbs")) {
117 | onlyPbsImages.add(url);
118 | }
119 | }
120 | return onlyPbsImages;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/adapters/SearchFragmentPagerAdapter.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.adapters;
2 |
3 | import android.support.v4.app.Fragment;
4 | import android.support.v4.app.FragmentManager;
5 | import android.support.v4.app.FragmentPagerAdapter;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class SearchFragmentPagerAdapter extends FragmentPagerAdapter {
11 |
12 | private List mFragmentList = new ArrayList<>();
13 | private List mFragmentNameList = new ArrayList<>();
14 |
15 | public SearchFragmentPagerAdapter(FragmentManager fm) {
16 | super(fm);
17 | }
18 |
19 | @Override
20 | public Fragment getItem(int position) {
21 | return mFragmentList.get(position);
22 | }
23 |
24 | @Override
25 | public int getCount() {
26 | return mFragmentList.size();
27 | }
28 |
29 | @Override
30 | public CharSequence getPageTitle(int position) {
31 | return mFragmentNameList.get(position);
32 | }
33 |
34 | public void addFragment(Fragment fragment, String pageTitle) {
35 | mFragmentList.add(fragment);
36 | mFragmentNameList.add(pageTitle);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/adapters/SuggestAdapter.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.adapters;
2 |
3 |
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import org.loklak.wok.model.suggest.Query;
11 | import org.loklak.wok.R;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import butterknife.BindView;
17 | import butterknife.ButterKnife;
18 |
19 |
20 | public class SuggestAdapter extends RecyclerView.Adapter {
21 |
22 | private List mQueries = new ArrayList<>();
23 | private OnSuggestionClickListener mSuggestionClickListener;
24 |
25 | public SuggestAdapter(List queries, OnSuggestionClickListener clickListener) {
26 | mQueries = queries;
27 | mSuggestionClickListener = clickListener;
28 | }
29 |
30 | public interface OnSuggestionClickListener {
31 | void onSuggestionClicked(Query query);
32 | }
33 |
34 | @Override
35 | public SuggestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
36 | LayoutInflater inflater = LayoutInflater.from(parent.getContext());
37 | View view = inflater.inflate(R.layout.row_tweet_search_suggest, parent, false);
38 | return new SuggestViewHolder(view);
39 | }
40 |
41 | @Override
42 | public void onBindViewHolder(SuggestViewHolder holder, int position) {
43 | holder.bind(mQueries.get(position));
44 | }
45 |
46 | @Override
47 | public int getItemCount() {
48 | return mQueries.size();
49 | }
50 |
51 | public void setQueries(List queries) {
52 | mQueries = queries;
53 | notifyDataSetChanged();
54 | }
55 |
56 | public List getQueries() {
57 | return mQueries;
58 | }
59 |
60 | class SuggestViewHolder extends RecyclerView.ViewHolder {
61 |
62 | @BindView(R.id.suggest_query)
63 | TextView suggestQuery;
64 |
65 | SuggestViewHolder(View itemView) {
66 | super(itemView);
67 | ButterKnife.bind(this, itemView);
68 | itemView.setOnClickListener(view -> {
69 | int position = getLayoutPosition();
70 | Query query = mQueries.get(position);
71 | mSuggestionClickListener.onSuggestionClicked(query);
72 | });
73 | }
74 |
75 | void bind(Query query) {
76 | suggestQuery.setText(query.getQuery());
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/adapters/TweetImagesAdapter.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.adapters;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 |
10 | import com.bumptech.glide.Glide;
11 |
12 | import org.loklak.wok.R;
13 |
14 | import java.util.List;
15 |
16 | import butterknife.BindView;
17 | import butterknife.ButterKnife;
18 |
19 | public class TweetImagesAdapter extends
20 | RecyclerView.Adapter {
21 |
22 | private Context mContext;
23 | private List mImageUrls;
24 |
25 | public TweetImagesAdapter(Context context, List imageUrls) {
26 | this.mContext = context;
27 | this.mImageUrls = imageUrls;
28 | }
29 |
30 | @Override
31 | public TweetImagesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
32 | LayoutInflater layoutInflater = LayoutInflater.from(mContext);
33 | View view = layoutInflater.inflate(R.layout.grid_elem_tweet_photo, parent, false);
34 | return new TweetImagesViewHolder(view);
35 | }
36 |
37 | @Override
38 | public void onBindViewHolder(TweetImagesViewHolder holder, int position) {
39 | holder.bind(mImageUrls.get(position));
40 | }
41 |
42 | @Override
43 | public int getItemCount() {
44 | return mImageUrls.size();
45 | }
46 |
47 | class TweetImagesViewHolder extends RecyclerView.ViewHolder {
48 |
49 | @BindView(R.id.tweet_image)
50 | ImageView tweetImage;
51 |
52 | public TweetImagesViewHolder(View itemView) {
53 | super(itemView);
54 | ButterKnife.bind(this, itemView);
55 | }
56 |
57 | void bind(String imageUrl) {
58 | Glide.with(mContext).load(imageUrl).fitCenter().into(tweetImage);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/loklak/LoklakAPI.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.loklak;
2 |
3 | import org.loklak.wok.model.harvest.Push;
4 | import org.loklak.wok.model.search.Search;
5 | import org.loklak.wok.model.suggest.SuggestData;
6 |
7 | import io.reactivex.Observable;
8 | import retrofit2.http.Field;
9 | import retrofit2.http.FormUrlEncoded;
10 | import retrofit2.http.GET;
11 | import retrofit2.http.POST;
12 | import retrofit2.http.Query;
13 |
14 |
15 | public interface LoklakAPI {
16 |
17 | @GET("/api/suggest.json")
18 | Observable getSuggestions(@Query("q") String query);
19 |
20 | @GET("/api/suggest.json")
21 | Observable getSuggestions(@Query("q") String query, @Query("count") int count);
22 |
23 | @POST("/api/push.json")
24 | @FormUrlEncoded
25 | Observable pushTweetsToLoklak(@Field("data") String data);
26 |
27 | @GET("api/search.json")
28 | Observable getSearchedTweets(
29 | @Query("q") String query,
30 | @Query("filter") String filter,
31 | @Query("count") int count);
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/loklak/RestClient.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.loklak;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import org.loklak.wok.Utility;
6 |
7 | import retrofit2.Retrofit;
8 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
9 | import retrofit2.converter.gson.GsonConverterFactory;
10 |
11 |
12 | public class RestClient {
13 |
14 | private static final String BASE_URL = "https://api.loklak.org/";
15 |
16 | private static Gson gson = Utility.getGsonForPrivateVariableClass();
17 | private static Retrofit sRetrofit;
18 |
19 | private RestClient() {
20 | }
21 |
22 | private static void createRestClient() {
23 | sRetrofit = new Retrofit.Builder()
24 | .baseUrl(BASE_URL)
25 | .addConverterFactory(GsonConverterFactory.create(gson))
26 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
27 | .build();
28 | }
29 |
30 | private static Retrofit getRetrofitInstance() {
31 | if (sRetrofit == null) {
32 | createRestClient();
33 | }
34 | return sRetrofit;
35 | }
36 |
37 | public static T createApi(Class apiInterface) {
38 | return getRetrofitInstance().create(apiInterface);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/twitter/TwitterAPI.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.twitter;
2 |
3 |
4 | import org.loklak.wok.model.twitter.AccountVerifyCredentials;
5 | import org.loklak.wok.model.twitter.StatusUpdate;
6 |
7 | import io.reactivex.Observable;
8 | import okhttp3.ResponseBody;
9 | import retrofit2.http.Field;
10 | import retrofit2.http.FormUrlEncoded;
11 | import retrofit2.http.GET;
12 | import retrofit2.http.POST;
13 |
14 | public interface TwitterAPI {
15 |
16 | String BASE_URL = "https://api.twitter.com/";
17 |
18 | /**
19 | * Used to obtain the request token, the first step in 3-legged authorization.
20 | * For more, please refer to: https://dev.twitter.com/web/sign-in/implementing
21 | * API doc link: https://dev.twitter.com/oauth/reference/post/oauth/request_token
22 | * @param oauthCallback The callback url for redirecting, after user allows the client app.
23 | * @return
24 | */
25 | @FormUrlEncoded
26 | @POST("/oauth/request_token")
27 | Observable getRequestToken(@Field("oauth_callback") String oauthCallback);
28 |
29 | /**
30 | * Oauth access token and access token secret are fetched using the oauth_verifier obtained in
31 | * step 2 of 3-legged authorization for calling other twitter API endpoints.
32 | * For more, please refer to: https://dev.twitter.com/web/sign-in/implementing
33 | * API dock link: https://dev.twitter.com/oauth/reference/post/oauth/access_token
34 | * @param oauthVerifier
35 | * @return
36 | */
37 | @FormUrlEncoded
38 | @POST("/oauth/access_token")
39 | Observable getAccessTokenAndSecret(@Field("oauth_verifier") String oauthVerifier);
40 |
41 | /**
42 | * User account details like user's full name, username, userid, profile image url are fetched.
43 | * API doc link: https://dev.twitter.com/rest/reference/get/account/verify_credentials
44 | * @return
45 | */
46 | @GET("/1.1/account/verify_credentials.json")
47 | Observable getAccountCredentials();
48 |
49 | /**
50 | * Used to post tweet.
51 | * API doc link: https://dev.twitter.com/rest/reference/post/statuses/update
52 | * @param status Text of the tweet
53 | * @param mediaIds Image ids, to be posted along with text.
54 | * @param latitude Latitude of user's location
55 | * @param longitude Longitude of user's location
56 | * @return
57 | */
58 | @FormUrlEncoded
59 | @POST("/1.1/statuses/update.json")
60 | Observable postTweet(
61 | @Field("status") String status,
62 | @Field("media_ids") String mediaIds,
63 | @Field("lat") Double latitude,
64 | @Field("long") Double longitude);
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/twitter/TwitterMediaAPI.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.twitter;
2 |
3 |
4 | import org.loklak.wok.model.twitter.MediaUpload;
5 |
6 | import io.reactivex.Observable;
7 | import okhttp3.RequestBody;
8 | import retrofit2.http.Multipart;
9 | import retrofit2.http.POST;
10 | import retrofit2.http.Part;
11 |
12 | public interface TwitterMediaAPI {
13 |
14 | String BASE_URL = "https://upload.twitter.com/";
15 |
16 | /**
17 | * Sends a multipart POST request, to obtain the image id, which can be passed as
18 | * mediaIds
parameter for posting tweet with images, in
19 | * {@link TwitterAPI#postTweet(String, String, Double, Double)}
20 | * method.
21 | * @param rawBinary Raw binary of image file.
22 | * @param mediaData Base64 encoded string of image file.
23 | * @return
24 | */
25 | @Multipart
26 | @POST("/1.1/media/upload.json")
27 | Observable getMediaId(
28 | @Part("media") RequestBody rawBinary,
29 | @Part("media_data") RequestBody mediaData
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/twitter/TwitterMediaRestClient.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.twitter;
2 |
3 |
4 | import org.loklak.wok.utility.Constants;
5 |
6 | import java.util.Random;
7 |
8 | import okhttp3.OkHttpClient;
9 | import okhttp3.logging.HttpLoggingInterceptor;
10 | import retrofit2.Retrofit;
11 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
12 | import retrofit2.converter.gson.GsonConverterFactory;
13 |
14 | public class TwitterMediaRestClient {
15 |
16 | private static TwitterOAuthInterceptor.Builder sMediaOAuthInterceptor =
17 | new TwitterOAuthInterceptor.Builder()
18 | .consumerKey(Constants.KEY)
19 | .consumerSecret(Constants.SECRET)
20 | .random(new Random())
21 | .clock(new TwitterOAuthInterceptor.Clock())
22 | .onlyOauthParams(true);
23 |
24 | public static TwitterMediaAPI createTwitterMediaAPI(String accessToken, String accessSecret) {
25 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
26 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
27 | TwitterOAuthInterceptor mediaInterceptor = sMediaOAuthInterceptor
28 | .accessToken(accessToken)
29 | .accessSecret(accessSecret).build();
30 | OkHttpClient okHttpClient = new OkHttpClient.Builder()
31 | .addInterceptor(mediaInterceptor)
32 | //.addInterceptor(loggingInterceptor) // uncomment to debug network requests
33 | .build();
34 | Retrofit retrofit = new Retrofit.Builder()
35 | .baseUrl(TwitterMediaAPI.BASE_URL)
36 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
37 | .addConverterFactory(GsonConverterFactory.create())
38 | .client(okHttpClient)
39 | .build();
40 | return retrofit.create(TwitterMediaAPI.class);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/twitter/TwitterOAuthInterceptor.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.twitter;
2 |
3 | /*
4 | * Copyright (C) 2015 Jake Wharton
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import java.io.IOException;
20 | import java.security.InvalidKeyException;
21 | import java.security.NoSuchAlgorithmException;
22 | import java.security.SecureRandom;
23 | import java.util.Map;
24 | import java.util.Random;
25 | import java.util.SortedMap;
26 | import java.util.TreeMap;
27 |
28 | import javax.crypto.Mac;
29 | import javax.crypto.spec.SecretKeySpec;
30 |
31 | import okhttp3.HttpUrl;
32 | import okhttp3.Interceptor;
33 | import okhttp3.Request;
34 | import okhttp3.RequestBody;
35 | import okhttp3.Response;
36 | import okio.Buffer;
37 | import okio.ByteString;
38 |
39 | public final class TwitterOAuthInterceptor implements Interceptor {
40 | private static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
41 | private static final String OAUTH_NONCE = "oauth_nonce";
42 | private static final String OAUTH_SIGNATURE = "oauth_signature";
43 | private static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
44 | private static final String OAUTH_SIGNATURE_METHOD_VALUE = "HMAC-SHA1";
45 | private static final String OAUTH_TIMESTAMP = "oauth_timestamp";
46 | private static final String OAUTH_ACCESS_TOKEN = "oauth_token";
47 | private static final String OAUTH_VERSION = "oauth_version";
48 | private static final String OAUTH_VERSION_VALUE = "1.0";
49 |
50 | private final String consumerKey;
51 | private final String consumerSecret;
52 | private final String accessToken;
53 | private final String accessSecret;
54 | private final Random random;
55 | private final Clock clock;
56 | private Boolean onlyOauthParams;
57 |
58 | private TwitterOAuthInterceptor(
59 | String consumerKey, String consumerSecret, String accessToken,
60 | String accessSecret, Random random, Clock clock, Boolean onlyOauthParams) {
61 | this.consumerKey = consumerKey;
62 | this.consumerSecret = consumerSecret;
63 | this.accessToken = accessToken;
64 | this.accessSecret = accessSecret;
65 | this.random = random;
66 | this.clock = clock;
67 | if (onlyOauthParams == null) {
68 | this.onlyOauthParams = false;
69 | } else {
70 | this.onlyOauthParams = onlyOauthParams;
71 | }
72 | }
73 |
74 | @Override
75 | public Response intercept(Chain chain) throws IOException {
76 | return chain.proceed(signRequest(chain.request()));
77 | }
78 |
79 | public Request signRequest(Request request) throws IOException {
80 | byte[] nonce = new byte[32];
81 | random.nextBytes(nonce);
82 | String oauthNonce = ByteString.of(nonce).base64().replaceAll("\\W", "");
83 | String oauthTimestamp = clock.millis();
84 |
85 | String consumerKeyValue = UrlEscapeUtils.escape(consumerKey);
86 | String accessTokenValue = UrlEscapeUtils.escape(accessToken);
87 |
88 | SortedMap parameters = new TreeMap<>();
89 | parameters.put(OAUTH_CONSUMER_KEY, consumerKeyValue);
90 | parameters.put(OAUTH_ACCESS_TOKEN, accessTokenValue);
91 | parameters.put(OAUTH_NONCE, oauthNonce);
92 | parameters.put(OAUTH_TIMESTAMP, oauthTimestamp);
93 | parameters.put(OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD_VALUE);
94 | parameters.put(OAUTH_VERSION, OAUTH_VERSION_VALUE);
95 |
96 | if (!onlyOauthParams) {
97 | HttpUrl url = request.url();
98 | for (int i = 0; i < url.querySize(); i++) {
99 | parameters.put(UrlEscapeUtils.escape(url.queryParameterName(i)),
100 | UrlEscapeUtils.escape(url.queryParameterValue(i)));
101 | }
102 |
103 | Buffer body = new Buffer();
104 |
105 | RequestBody requestBody = request.body();
106 | if (requestBody != null) {
107 | requestBody.writeTo(body);
108 | }
109 |
110 | while (!body.exhausted()) {
111 | long keyEnd = body.indexOf((byte) '=');
112 | if (keyEnd == -1)
113 | throw new IllegalStateException("Key with no value: " + body.readUtf8());
114 | String key = body.readUtf8(keyEnd);
115 | body.skip(1); // Equals.
116 |
117 | long valueEnd = body.indexOf((byte) '&');
118 | String value = valueEnd == -1 ? body.readUtf8() : body.readUtf8(valueEnd);
119 | if (valueEnd != -1) body.skip(1); // Ampersand.
120 |
121 | parameters.put(key, value);
122 | }
123 | }
124 |
125 | Buffer base = new Buffer();
126 | String method = request.method();
127 | base.writeUtf8(method);
128 | base.writeByte('&');
129 | base.writeUtf8(
130 | UrlEscapeUtils.escape(request.url().newBuilder().query(null).build().toString()));
131 | base.writeByte('&');
132 |
133 | boolean first = true;
134 | for (Map.Entry entry : parameters.entrySet()) {
135 | if (!first) base.writeUtf8(UrlEscapeUtils.escape("&"));
136 | first = false;
137 | base.writeUtf8(UrlEscapeUtils.escape(entry.getKey()));
138 | base.writeUtf8(UrlEscapeUtils.escape("="));
139 | base.writeUtf8(UrlEscapeUtils.escape(entry.getValue()));
140 | }
141 |
142 | String signingKey =
143 | UrlEscapeUtils.escape(consumerSecret) + "&" + UrlEscapeUtils.escape(accessSecret);
144 |
145 | SecretKeySpec keySpec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1");
146 | Mac mac;
147 | try {
148 | mac = Mac.getInstance("HmacSHA1");
149 | mac.init(keySpec);
150 | } catch (NoSuchAlgorithmException | InvalidKeyException e) {
151 | throw new IllegalStateException(e);
152 | }
153 | byte[] result = mac.doFinal(base.readByteArray());
154 | String signature = ByteString.of(result).base64();
155 |
156 | String authorization =
157 | "OAuth " + OAUTH_CONSUMER_KEY + "=\"" + consumerKeyValue + "\", " + OAUTH_NONCE + "=\""
158 | + oauthNonce + "\", " + OAUTH_SIGNATURE + "=\"" + UrlEscapeUtils.escape(signature)
159 | + "\", " + OAUTH_SIGNATURE_METHOD + "=\"" + OAUTH_SIGNATURE_METHOD_VALUE + "\", "
160 | + OAUTH_TIMESTAMP + "=\"" + oauthTimestamp + "\", " + OAUTH_ACCESS_TOKEN + "=\""
161 | + accessTokenValue + "\", " + OAUTH_VERSION + "=\"" + OAUTH_VERSION_VALUE + "\"";
162 |
163 | return request.newBuilder().addHeader("Authorization", authorization).build();
164 | }
165 |
166 | public static final class Builder {
167 | private String consumerKey;
168 | private String consumerSecret;
169 | private String accessToken;
170 | private String accessSecret;
171 | private Random random = new SecureRandom();
172 | private Clock clock = new Clock();
173 | private Boolean onlyOauthParams;
174 |
175 | public Builder consumerKey(String consumerKey) {
176 | if (consumerKey == null) throw new NullPointerException("consumerKey = null");
177 | this.consumerKey = consumerKey;
178 | return this;
179 | }
180 |
181 | public Builder consumerSecret(String consumerSecret) {
182 | if (consumerSecret == null) throw new NullPointerException("consumerSecret = null");
183 | this.consumerSecret = consumerSecret;
184 | return this;
185 | }
186 |
187 | public Builder accessToken(String accessToken) {
188 | if (accessToken == null) throw new NullPointerException("accessToken == null");
189 | this.accessToken = accessToken;
190 | return this;
191 | }
192 |
193 | public Builder accessSecret(String accessSecret) {
194 | if (accessSecret == null) throw new NullPointerException("accessSecret == null");
195 | this.accessSecret = accessSecret;
196 | return this;
197 | }
198 |
199 | public Builder random(Random random) {
200 | if (random == null) throw new NullPointerException("random == null");
201 | this.random = random;
202 | return this;
203 | }
204 |
205 | public Builder clock(Clock clock) {
206 | if (clock == null) throw new NullPointerException("clock == null");
207 | this.clock = clock;
208 | return this;
209 | }
210 |
211 | public Builder onlyOauthParams(Boolean onlyOauthParams) {
212 | if (onlyOauthParams == null) this.onlyOauthParams = false;
213 | else this.onlyOauthParams = onlyOauthParams;
214 | return this;
215 | }
216 |
217 | public TwitterOAuthInterceptor build() {
218 | if (consumerKey == null) throw new IllegalStateException("consumerKey not set");
219 | if (consumerSecret == null) throw new IllegalStateException("consumerSecret not set");
220 | if (accessToken == null) throw new IllegalStateException("accessToken not set");
221 | if (accessSecret == null) throw new IllegalStateException("accessSecret not set");
222 | return new TwitterOAuthInterceptor(consumerKey, consumerSecret, accessToken, accessSecret,
223 | random, clock, onlyOauthParams);
224 | }
225 | }
226 |
227 | /** Simple clock like class, to allow time mocking. */
228 | public static class Clock {
229 | /** Returns the current time in milliseconds divided by 1K. */
230 | public String millis() {
231 | return Long.toString(System.currentTimeMillis() / 1000L);
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/api/twitter/TwitterRestClient.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.api.twitter;
2 |
3 |
4 | import com.google.gson.Gson;
5 |
6 | import org.loklak.wok.Utility;
7 | import org.loklak.wok.utility.Constants;
8 |
9 | import java.util.Random;
10 |
11 | import okhttp3.OkHttpClient;
12 | import okhttp3.logging.HttpLoggingInterceptor;
13 | import retrofit2.Retrofit;
14 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
15 | import retrofit2.converter.gson.GsonConverterFactory;
16 |
17 | public class TwitterRestClient {
18 |
19 | // basic builders created
20 | private static TwitterOAuthInterceptor.Builder sInterceptorBuilder =
21 | new TwitterOAuthInterceptor.Builder()
22 | .consumerKey(Constants.KEY)
23 | .consumerSecret(Constants.SECRET)
24 | .random(new Random())
25 | .clock(new TwitterOAuthInterceptor.Clock());
26 |
27 | private static Gson mGson = Utility.getGsonForPrivateVariableClass();
28 | private static Retrofit.Builder sRetrofitBuilder = new Retrofit.Builder()
29 | .baseUrl(TwitterAPI.BASE_URL)
30 | .addConverterFactory(GsonConverterFactory.create(mGson))
31 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create());
32 |
33 | // without access_token interceptor and client created
34 | private static TwitterOAuthInterceptor sWithoutAccessTokenInterceptor =
35 | sInterceptorBuilder.accessToken("").accessSecret("").build();
36 |
37 | // logger for debugging
38 | private static HttpLoggingInterceptor sLoggingInterceptor = new HttpLoggingInterceptor();
39 |
40 | private static OkHttpClient.Builder sWithoutAccessTokenClient = new OkHttpClient.Builder()
41 | .addInterceptor(sWithoutAccessTokenInterceptor);
42 |
43 | private static Retrofit sWithoutAccessTokenRetrofit;
44 |
45 |
46 | public static TwitterAPI createTwitterAPIWithoutAccessToken() {
47 | if (sWithoutAccessTokenRetrofit == null) {
48 | sLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
49 | // uncomment to debug network requests
50 | // sWithoutAccessTokenClient.addInterceptor(sLoggingInterceptor);
51 | sWithoutAccessTokenRetrofit = sRetrofitBuilder
52 | .client(sWithoutAccessTokenClient.build()).build();
53 | }
54 | return sWithoutAccessTokenRetrofit.create(TwitterAPI.class);
55 | }
56 |
57 | public static TwitterAPI createTwitterAPIWithAccessToken(String token) {
58 | TwitterOAuthInterceptor withAccessTokenInterceptor =
59 | sInterceptorBuilder.accessToken(token).accessSecret("").build();
60 | OkHttpClient withAccessTokenClient = new OkHttpClient.Builder()
61 | .addInterceptor(withAccessTokenInterceptor)
62 | //.addInterceptor(loggingInterceptor) // uncomment to debug network requests
63 | .build();
64 | Retrofit withAccessTokenRetrofit = sRetrofitBuilder.client(withAccessTokenClient).build();
65 | return withAccessTokenRetrofit.create(TwitterAPI.class);
66 | }
67 |
68 | public static TwitterAPI createTwitterAPIWithAccessTokenAndSecret(
69 | String token, String tokenSecret) {
70 | sLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
71 | TwitterOAuthInterceptor withAccessTokenAndSecretInterceptor =
72 | sInterceptorBuilder.accessToken(token).accessSecret(tokenSecret).build();
73 | OkHttpClient withAccessTokenAndSecretClient = new OkHttpClient.Builder()
74 | .addInterceptor(withAccessTokenAndSecretInterceptor)
75 | //.addInterceptor(loggingInterceptor) // uncomment to debug network requests
76 | .build();
77 | Retrofit withAccessTokenAndSecretRetrofit =
78 | sRetrofitBuilder.client(withAccessTokenAndSecretClient).build();
79 | return withAccessTokenAndSecretRetrofit.create(TwitterAPI.class);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/inject/ApplicationComponent.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.inject;
2 |
3 |
4 | import org.loklak.wok.ui.suggestion.SuggestFragment;
5 |
6 | import dagger.Component;
7 |
8 | @Component(modules = ApplicationModule.class)
9 | public interface ApplicationComponent {
10 |
11 |
12 | void inject(SuggestFragment suggestFragment);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/inject/ApplicationModule.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.inject;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import org.loklak.wok.Utility;
6 | import org.loklak.wok.api.loklak.LoklakAPI;
7 |
8 | import dagger.Module;
9 | import dagger.Provides;
10 | import io.realm.Realm;
11 | import retrofit2.Retrofit;
12 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
13 | import retrofit2.converter.gson.GsonConverterFactory;
14 |
15 |
16 | @Module
17 | public class ApplicationModule {
18 |
19 | private String mBaseUrl;
20 |
21 | public ApplicationModule(String baseUrl) {
22 | this.mBaseUrl = baseUrl;
23 | }
24 |
25 | @Provides
26 | Retrofit providesRetrofit() {
27 | Gson gson = Utility.getGsonForPrivateVariableClass();
28 | return new Retrofit.Builder()
29 | .baseUrl(mBaseUrl)
30 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
31 | .addConverterFactory(GsonConverterFactory.create(gson))
32 | .build();
33 | }
34 |
35 | @Provides
36 | LoklakAPI providesLoklakAPI(Retrofit retrofit) {
37 | return retrofit.create(LoklakAPI.class);
38 | }
39 |
40 | @Provides
41 | Realm providesRealm() {
42 | return Realm.getDefaultInstance();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/harvest/Push.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.harvest;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class Push {
6 |
7 | private String mStatus;
8 | private int mRecords;
9 | @SerializedName("mps")
10 | private int mMessagesPerSecond;
11 | private String mMessage;
12 |
13 | public String getStatus() {
14 | return mStatus;
15 | }
16 |
17 | public int getRecords() {
18 | return mRecords;
19 | }
20 |
21 | public int getMps() {
22 | return mMessagesPerSecond;
23 | }
24 |
25 | public String getMessage() {
26 | return mMessage;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/harvest/ScrapedData.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.harvest;
2 |
3 | import java.util.List;
4 |
5 | public class ScrapedData {
6 |
7 | private List mStatuses;
8 | private String mQuery;
9 |
10 | public List getStatuses() {
11 | return mStatuses;
12 | }
13 |
14 | public void setStatuses(List statuses) {
15 | this.mStatuses = statuses;
16 | }
17 |
18 | public String getQuery() {
19 | return mQuery;
20 | }
21 |
22 | public void setQuery(String mQuery) {
23 | this.mQuery = mQuery;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/harvest/Status.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.harvest;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public class Status implements Parcelable{
10 |
11 | private User mUser;
12 | private String mScreenName;
13 | private String mLink;
14 | private long mCreatedAt;
15 | private long mTimestamp;
16 | private String mText;
17 | private String mIdStr;
18 | private Integer mRetweetCount;
19 | private Integer mFavouritesCount;
20 | private Double[] mlocationPoint;
21 | private List mImages;
22 | private List mVideos;
23 |
24 | public Status(User user, String screenName, String link, Long createdAt,
25 | Long timeStamp, String text, String id, int retweetCount) {
26 | this.mUser = user;
27 | this.mScreenName = screenName;
28 | this.mLink = link;
29 | this.mCreatedAt = createdAt;
30 | this.mTimestamp = timeStamp;
31 | this.mText = text;
32 | this.mIdStr = id;
33 | this.mRetweetCount = retweetCount;
34 | this.mFavouritesCount = 0;
35 | mImages = new ArrayList<>();
36 | mVideos = new ArrayList<>();
37 | }
38 |
39 | public User getUser() {
40 | return mUser;
41 | }
42 |
43 | public String getmScreenName() {
44 | return mScreenName;
45 | }
46 |
47 | public String getLink() {
48 | return mLink;
49 | }
50 |
51 | public long getCreatedAt() {
52 | return mCreatedAt;
53 | }
54 |
55 | public long getTimestamp() {
56 | return mTimestamp;
57 | }
58 |
59 | public String getText() {
60 | return mText;
61 | }
62 |
63 | public String getIdStr() {
64 | return mIdStr;
65 | }
66 |
67 | public Integer getRetweetCount() {
68 | return mRetweetCount;
69 | }
70 |
71 | public Integer getFavouritesCount() {
72 | return mFavouritesCount;
73 | }
74 |
75 | public List getImages() {
76 | return mImages;
77 | }
78 |
79 | public List getVideos() {
80 | return mVideos;
81 | }
82 |
83 | public void setUser(User mUser) {
84 | this.mUser = mUser;
85 | }
86 |
87 | public void setScreenName(String mScreenName) {
88 | this.mScreenName = mScreenName;
89 | }
90 |
91 | public void setLink(String mLink) {
92 | this.mLink = mLink;
93 | }
94 |
95 | public void setCreatedAt(long mCreatedAt) {
96 | this.mCreatedAt = mCreatedAt;
97 | }
98 |
99 | public void setTimestamp(long mTimestamp) {
100 | this.mTimestamp = mTimestamp;
101 | }
102 |
103 | public void setText(String mText) {
104 | this.mText = mText;
105 | }
106 |
107 | public void setIdStr(String mIdStr) {
108 | this.mIdStr = mIdStr;
109 | }
110 |
111 | public void setRetweetCount(Integer mRetweetCount) {
112 | this.mRetweetCount = mRetweetCount;
113 | }
114 |
115 | public void setFavouritesCount(Integer mFavouritesCount) {
116 | this.mFavouritesCount = mFavouritesCount;
117 | }
118 |
119 | public void setLocationPoint(Double latitude, Double longitude) {
120 | mlocationPoint = new Double[2];
121 | mlocationPoint[0] = longitude;
122 | mlocationPoint[1] = latitude;
123 | }
124 |
125 | public void setImages(List mImages) {
126 | this.mImages = mImages;
127 | }
128 |
129 | public void setVideos(List mVideos) {
130 | this.mVideos = mVideos;
131 | }
132 |
133 | @Override
134 | public int describeContents() {
135 | return 0;
136 | }
137 |
138 | @Override
139 | public void writeToParcel(Parcel dest, int flags) {
140 | dest.writeParcelable(mUser, flags);
141 | dest.writeLong(mCreatedAt);
142 | dest.writeString(mText);
143 | }
144 |
145 | private Status(Parcel source) {
146 | mUser = source.readParcelable(User.class.getClassLoader());
147 | mCreatedAt = source.readLong();
148 | mText = source.readString();
149 | }
150 |
151 | public static final Parcelable.Creator CREATOR = new Creator() {
152 | @Override
153 | public Status createFromParcel(Parcel source) {
154 | return new Status(source);
155 | }
156 |
157 | @Override
158 | public Status[] newArray(int size) {
159 | return new Status[size];
160 | }
161 | };
162 | }
163 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/harvest/User.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.harvest;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | public class User implements Parcelable {
7 |
8 | private String mName;
9 | private String mScreenName;
10 | private String mProfileImageUrlHttps;
11 | private String mUserId;
12 | private String mAppearanceFirst;
13 | private String mAppearanceLatest;
14 |
15 | public User(String name, String screenName, String profileImageUrlHttps,
16 | String userId, String appearanceFirst, String appearanceLatest) {
17 | this.mName = name;
18 | this.mScreenName = screenName;
19 | this.mProfileImageUrlHttps = profileImageUrlHttps;
20 | this.mUserId = userId;
21 | this.mAppearanceFirst = appearanceFirst;
22 | this.mAppearanceLatest = appearanceLatest;
23 | }
24 |
25 | public String getProfileImageUrlHttps() {
26 | return mProfileImageUrlHttps;
27 | }
28 |
29 | public String getName() {
30 | return mName;
31 | }
32 |
33 | public String getScreenName() {
34 | return mScreenName;
35 | }
36 |
37 |
38 | public void setAppearanceFirst(String mAppearanceFirst) {
39 | this.mAppearanceFirst = mAppearanceFirst;
40 | }
41 |
42 | public void setAppearanceLatest(String mAppearanceLatest) {
43 | this.mAppearanceLatest = mAppearanceLatest;
44 | }
45 |
46 | public void setUserId(String mUserId) {
47 | this.mUserId = mUserId;
48 | }
49 |
50 | public void setProfileImageUrlHttps(String mProfileImageUrlHttps) {
51 | this.mProfileImageUrlHttps = mProfileImageUrlHttps;
52 | }
53 |
54 | public void setName(String mName) {
55 | this.mName = mName;
56 | }
57 |
58 | public void setScreenName(String mScreenName) {
59 | this.mScreenName = mScreenName;
60 | }
61 |
62 | @Override
63 | public int describeContents() {
64 | return 0;
65 | }
66 |
67 | @Override
68 | public void writeToParcel(Parcel dest, int flags) {
69 | dest.writeString(mName);
70 | dest.writeString(mScreenName);
71 | }
72 |
73 | private User(Parcel parcel) {
74 | mName = parcel.readString();
75 | mScreenName = parcel.readString();
76 | }
77 |
78 | public static final Parcelable.Creator CREATOR = new Creator() {
79 | @Override
80 | public User createFromParcel(Parcel source) {
81 | return new User(source);
82 | }
83 |
84 | @Override
85 | public User[] newArray(int size) {
86 | return new User[size];
87 | }
88 | };
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/search/Search.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.search;
2 |
3 | import java.util.List;
4 |
5 | public class
6 |
7 |
8 | Search {
9 |
10 | private SearchMetadata mSearchMetadata;
11 | private List mStatuses = null;
12 | private List aggregations;
13 |
14 | public List getStatuses() {
15 | return mStatuses;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/search/SearchMetadata.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.search;
2 |
3 |
4 | import com.google.gson.annotations.SerializedName;
5 |
6 | public class SearchMetadata {
7 |
8 | @SerializedName("startRecord")
9 | private String mStartRecord;
10 | @SerializedName("maximumRecords")
11 | private String mMaximumRecords;
12 | private String mCount;
13 | private Integer mHits;
14 | private Long mPeriod;
15 | private String mQuery;
16 | private String mFilter;
17 | private String mClient;
18 | private Integer mTime;
19 | private Integer mCountTwitterAll;
20 | private Integer mCountTwitterNew;
21 | private Integer mCountBackend;
22 | private Integer mCacheHits;
23 | private String mIndex;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/search/Status.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.search;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public class Status implements Parcelable{
10 |
11 | private String mTimestamp;
12 | private String mCreatedAt;
13 | private String mScreenName;
14 | private String mText;
15 | private String mLink;
16 | private String mIdStr;
17 | private Integer mRetweetCount;
18 | private Integer mFavouritesCount;
19 | private List mImages = new ArrayList<>();
20 | private List mAudio = null;
21 | private List mVideos = null;
22 | private User mUser;
23 |
24 | public String getTimestamp() {
25 | return mTimestamp;
26 | }
27 |
28 | public String getCreatedAt() {
29 | return mCreatedAt;
30 | }
31 |
32 | public String getText() {
33 | return mText;
34 | }
35 |
36 | public String getLink() {
37 | return mLink;
38 | }
39 |
40 | public String getIdStr() {
41 | return mIdStr;
42 | }
43 |
44 | public Integer getRetweetCount() {
45 | return mRetweetCount;
46 | }
47 |
48 | public Integer getFavouritesCount() {
49 | return mFavouritesCount;
50 | }
51 |
52 | public List getImages() {
53 | return mImages;
54 | }
55 |
56 | public List getAudio() {
57 | return mAudio;
58 | }
59 |
60 | public List getVideos() {
61 | return mVideos;
62 | }
63 |
64 | public User getUser() {
65 | return mUser;
66 | }
67 |
68 | @Override
69 | public int describeContents() {
70 | return 0;
71 | }
72 |
73 | @Override
74 | public void writeToParcel(Parcel dest, int flags) {
75 | dest.writeString(mText);
76 | dest.writeInt(mRetweetCount);
77 | dest.writeInt(mFavouritesCount);
78 | dest.writeStringList(mImages);
79 | dest.writeParcelable(mUser, flags);
80 | }
81 |
82 | private Status(Parcel source) {
83 | mText = source.readString();
84 | mRetweetCount = source.readInt();
85 | mFavouritesCount = source.readInt();
86 | mImages = source.createStringArrayList();
87 | mUser = source.readParcelable(User.class.getClassLoader());
88 | }
89 |
90 | public static final Parcelable.Creator CREATOR = new Creator() {
91 | @Override
92 | public Status createFromParcel(Parcel source) {
93 | return new Status(source);
94 | }
95 |
96 | @Override
97 | public Status[] newArray(int size) {
98 | return new Status[size];
99 | }
100 | };
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/search/User.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.search;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | public class User implements Parcelable {
7 |
8 | private String mProfileImageUrlHttps;
9 | private String mScreenName;
10 | private String mUserId;
11 | private String mName;
12 |
13 | public String getProfileImageUrlHttps() {
14 | return mProfileImageUrlHttps;
15 | }
16 |
17 | public String getScreenName() {
18 | return mScreenName;
19 | }
20 |
21 | public String getUserId() {
22 | return mUserId;
23 | }
24 |
25 | public String getName() {
26 | return mName;
27 | }
28 |
29 | @Override
30 | public int describeContents() {
31 | return 0;
32 | }
33 |
34 | @Override
35 | public void writeToParcel(Parcel dest, int flags) {
36 | dest.writeString(mProfileImageUrlHttps);
37 | dest.writeString(mName);
38 | dest.writeString(mScreenName);
39 | }
40 |
41 | private User(Parcel parcel) {
42 | mProfileImageUrlHttps = parcel.readString();
43 | mName = parcel.readString();
44 | mScreenName = parcel.readString();
45 | }
46 |
47 | public static final Parcelable.Creator CREATOR = new Creator() {
48 | @Override
49 | public User createFromParcel(Parcel source) {
50 | return new User(source);
51 | }
52 |
53 | @Override
54 | public User[] newArray(int size) {
55 | return new User[size];
56 | }
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/suggest/Query.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.suggest;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import io.realm.RealmObject;
6 |
7 |
8 | public class Query extends RealmObject {
9 |
10 | private String mQuery;
11 | private int mQueryCount;
12 | private int mMessagePeriod;
13 | private String mSourceType;
14 | private String mRetrievalLast;
15 | private int mMessagesPerDay;
16 | private int mQueryLength;
17 | @SerializedName("timezoneOffset")
18 | private int mTimezoneOffset;
19 | private String mRetrievalNext;
20 | private int mScoreRetrieval;
21 | private String mQueryLast;
22 | private String mExpectedNext;
23 | private int mScoreSuggest;
24 | private int mRetrievalCount;
25 | private String mQueryFirst;
26 |
27 | public Query() {
28 | mQuery = "";
29 | mSourceType = "";
30 | mRetrievalLast = "";
31 | mRetrievalNext = "";
32 | mQueryLast = "";
33 | mExpectedNext = "";
34 | mQueryFirst = "";
35 | }
36 |
37 | public void setQuery(String query) {
38 | this.mQuery = query;
39 | }
40 |
41 | public String getQuery() {
42 | return mQuery;
43 | }
44 |
45 | public int getQueryCount() {
46 | return mQueryCount;
47 | }
48 |
49 | public int getMessagePeriod() {
50 | return mMessagePeriod;
51 | }
52 |
53 | public String getSourceType() {
54 | return mSourceType;
55 | }
56 |
57 | public String getRetrievalLast() {
58 | return mRetrievalLast;
59 | }
60 |
61 | public int getMessagesPerDay() {
62 | return mMessagesPerDay;
63 | }
64 |
65 | public int getQueryLength() {
66 | return mQueryLength;
67 | }
68 |
69 | public int getTimezoneOffset() {
70 | return mTimezoneOffset;
71 | }
72 |
73 | public String getRetrievalNext() {
74 | return mRetrievalNext;
75 | }
76 |
77 | public int getScoreRetrieval() {
78 | return mScoreRetrieval;
79 | }
80 |
81 | public String getQueryLast() {
82 | return mQueryLast;
83 | }
84 |
85 | public String getExpectedNext() {
86 | return mExpectedNext;
87 | }
88 |
89 | public int getScoreSuggest() {
90 | return mScoreSuggest;
91 | }
92 |
93 | public int getRetrievalCount() {
94 | return mRetrievalCount;
95 | }
96 |
97 | public String getQueryFirst() {
98 | return mQueryFirst;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/suggest/SearchMetadata.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.suggest;
2 |
3 | public class SearchMetadata {
4 |
5 | private String mCount;
6 | private Integer mHits;
7 | private String mQuery;
8 | private String mOrder;
9 | private String mOrderby;
10 | private String mClient;
11 |
12 | public String getCount() {
13 | return mCount;
14 | }
15 |
16 | public Integer getHits() {
17 | return mHits;
18 | }
19 |
20 | public String getQuery() {
21 | return mQuery;
22 | }
23 |
24 | public String getOrder() {
25 | return mOrder;
26 | }
27 |
28 | public String getOrderby() {
29 | return mOrderby;
30 | }
31 |
32 | public String getClient() {
33 | return mClient;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/suggest/SuggestData.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.suggest;
2 |
3 |
4 | import java.util.List;
5 |
6 | public class SuggestData {
7 |
8 | private SearchMetadata mSearchMetadata;
9 | private List mQueries;
10 |
11 | public void setQueries(List queries) {
12 | this.mQueries = queries;
13 | }
14 |
15 | public SearchMetadata getSearchMetadata() {
16 | return mSearchMetadata;
17 | }
18 |
19 | public List getQueries() {
20 | return mQueries;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/twitter/AccountVerifyCredentials.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.twitter;
2 |
3 |
4 | public class AccountVerifyCredentials {
5 |
6 | private String mCreatedAt;
7 | private String mIdStr;
8 | private String mProfileImageUrlHttps;
9 | private String mName;
10 | private String mScreenName;
11 |
12 | public String getCreatedAt() {
13 | return mCreatedAt;
14 | }
15 |
16 | public String getIdStr() {
17 | return mIdStr;
18 | }
19 |
20 | public String getProfileImageUrlHttps() {
21 | return mProfileImageUrlHttps;
22 | }
23 |
24 | public String getName() {
25 | return mName;
26 | }
27 |
28 | public String getScreenName() {
29 | return mScreenName;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/twitter/MediaEntity.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.twitter;
2 |
3 |
4 | public class MediaEntity {
5 |
6 | private long mId;
7 | private String mIdStr;
8 | private String mMediaUrl;
9 | private String mMediaUrlHttps;
10 |
11 | public long getId() {
12 | return mId;
13 | }
14 |
15 | public String getIdStr() {
16 | return mIdStr;
17 | }
18 |
19 | public String getMediaUrl() {
20 | return mMediaUrl;
21 | }
22 |
23 | public String getMediaUrlHttps() {
24 | return mMediaUrlHttps;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/twitter/MediaUpload.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.twitter;
2 |
3 |
4 | import com.google.gson.annotations.SerializedName;
5 |
6 | public class MediaUpload {
7 |
8 | @SerializedName("media_id_string")
9 | private String mMediaIdString;
10 | private int mSize;
11 | private int mExpiresAfterSecs;
12 |
13 | public String getMediaIdString() {
14 | return mMediaIdString;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/twitter/StatusEntities.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.twitter;
2 |
3 |
4 | import java.util.List;
5 |
6 | public class StatusEntities {
7 |
8 | private List mMedia;
9 |
10 | public List getMediaList() {
11 | return mMedia;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/model/twitter/StatusUpdate.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.model.twitter;
2 |
3 |
4 | public class StatusUpdate {
5 |
6 | private String mIdStr;
7 | private String mText;
8 | private StatusEntities mExtendedEntities;
9 | private int mRetweetCount;
10 |
11 | public StatusUpdate(String idStr, String text, int retweetCount) {
12 | this.mIdStr = idStr;
13 | this.mText = text;
14 | this.mRetweetCount = retweetCount;
15 | }
16 |
17 | public String getIdStr() {
18 | return mIdStr;
19 | }
20 |
21 | public String getText() {
22 | return mText;
23 | }
24 |
25 | public StatusEntities getExtendedEntities() {
26 | return mExtendedEntities;
27 | }
28 |
29 | public int getRetweetCount() {
30 | return mRetweetCount;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/tools/LogLines.java:
--------------------------------------------------------------------------------
1 | /**
2 | * LogLines
3 | * Copyright 13.01.2016 by Michael Peter Christen, @0rb1t3r
4 | *
5 | * This library is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2.1 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public License
16 | * along with this program in the file lgpl21.txt
17 | * If not, see .
18 | */
19 |
20 |
21 | package org.loklak.wok.tools;
22 |
23 | import java.util.Iterator;
24 | import java.util.concurrent.BlockingQueue;
25 | import java.util.concurrent.LinkedBlockingQueue;
26 |
27 | /**
28 | * Implements a concurrent limited log line queue.
29 | * This shall be used to avoid memory leaks when the queue may
30 | * get too large and information from the queue can be abandoned safely.
31 | */
32 | public class LogLines implements Iterable {
33 |
34 | public final BlockingQueue lines = new LinkedBlockingQueue<>();
35 |
36 | private final int limit;
37 |
38 | public LogLines(int limit) {
39 | this.limit = limit;
40 | }
41 |
42 | public int size() {
43 | return lines.size();
44 | }
45 |
46 | public void add(A line) {
47 | this.lines.add(line);
48 | while (this.size() > this.limit) this.lines.poll();
49 | }
50 |
51 | public A poll() {
52 | return lines.poll();
53 | }
54 |
55 | public Iterator iterator() {
56 | return this.lines.iterator();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/activity/SearchActivity.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 |
7 | import org.loklak.wok.ui.fragment.SearchFragment;
8 | import org.loklak.wok.R;
9 |
10 | public class SearchActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | Intent intent = getIntent();
16 |
17 | setContentView(R.layout.activity_search);
18 | if (savedInstanceState == null) {
19 | SearchFragment searchFragment = new SearchFragment();
20 | getSupportFragmentManager()
21 | .beginTransaction()
22 | .replace(R.id.fragment_container, searchFragment)
23 | .commit();
24 | }
25 | }
26 |
27 | @Override
28 | public void onBackPressed() {
29 | super.onBackPressed();
30 | overridePendingTransition(R.anim.back_button_enter, R.anim.back_button_exit);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/activity/SplashActivity.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.support.v7.app.AppCompatActivity;
7 |
8 | import org.loklak.wok.R;
9 |
10 |
11 | public class SplashActivity extends AppCompatActivity {
12 |
13 | private Handler mHandler;
14 |
15 | private Runnable mRunnable = () -> {
16 | Intent intent = new Intent(this, TweetHarvestingActivity.class);
17 | startActivity(intent);
18 | finish();
19 | };
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_splash);
25 | mHandler = new Handler();
26 | }
27 |
28 | @Override
29 | protected void onResume() {
30 | super.onResume();
31 | mHandler.postDelayed(mRunnable, 3000);
32 | }
33 |
34 | @Override
35 | public void onBackPressed() {
36 | super.onBackPressed();
37 | removeCallback();
38 | }
39 |
40 | @Override
41 | protected void onStop() {
42 | super.onStop();
43 | removeCallback();
44 | }
45 |
46 | private void removeCallback() {
47 | if (mHandler != null) {
48 | mHandler.removeCallbacks(mRunnable);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/activity/TweetHarvestingActivity.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.activity;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.WindowManager;
7 | import android.widget.Toast;
8 |
9 | import org.loklak.wok.ui.fragment.TweetHarvestingFragment;
10 | import org.loklak.wok.R;
11 |
12 | public class TweetHarvestingActivity extends AppCompatActivity {
13 |
14 | private static long BackPressed;
15 |
16 | @Override
17 | protected void onCreate(@Nullable Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_tweet_harvesting);
20 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
21 | if (savedInstanceState == null) {
22 | TweetHarvestingFragment tweetHarvestingFragment = new TweetHarvestingFragment();
23 | getSupportFragmentManager()
24 | .beginTransaction()
25 | .replace(R.id.fragment_container, tweetHarvestingFragment)
26 | .commit();
27 | }
28 | }
29 |
30 | @Override
31 | public void onBackPressed() {
32 | if (BackPressed+2000>System.currentTimeMillis()) {
33 |
34 | super.onBackPressed();
35 | }
36 | else{
37 | Toast.makeText(getBaseContext(),"Press once again to exit",Toast.LENGTH_SHORT).show();
38 | }
39 | BackPressed = System.currentTimeMillis();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/activity/TweetPostingActivity.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.activity;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | import org.loklak.wok.ui.fragment.TweetPostingFragment;
7 | import org.loklak.wok.R;
8 |
9 |
10 | public class TweetPostingActivity extends AppCompatActivity {
11 |
12 | private final String LOG_TAG = TweetPostingActivity.class.getName();
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_tweet_posting);
18 | if (savedInstanceState == null) {
19 | TweetPostingFragment tweetPostingFragment = new TweetPostingFragment();
20 | getSupportFragmentManager()
21 | .beginTransaction()
22 | .replace(R.id.fragment_container, tweetPostingFragment)
23 | .commit();
24 | }
25 | }
26 |
27 | @Override
28 | public void onBackPressed() {
29 | super.onBackPressed();
30 | overridePendingTransition(R.anim.back_button_top_enter, R.anim.back_button_bottom_exit);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/fragment/SearchCategoryFragment.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.fragment;
2 |
3 |
4 | import android.content.res.Resources;
5 | import android.os.Bundle;
6 | import android.os.Parcelable;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.app.Fragment;
9 | import android.support.v7.widget.LinearLayoutManager;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.util.Log;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.ProgressBar;
16 | import android.widget.TextView;
17 |
18 | import org.loklak.wok.adapters.SearchCategoryAdapter;
19 | import org.loklak.wok.api.loklak.LoklakAPI;
20 | import org.loklak.wok.api.loklak.RestClient;
21 | import org.loklak.wok.model.search.Search;
22 | import org.loklak.wok.model.search.Status;
23 | import org.loklak.wok.utility.Constants;
24 | import org.loklak.wok.R;
25 |
26 | import java.util.ArrayList;
27 | import java.util.List;
28 |
29 | import butterknife.BindView;
30 | import butterknife.ButterKnife;
31 | import butterknife.OnClick;
32 | import io.reactivex.android.schedulers.AndroidSchedulers;
33 | import io.reactivex.schedulers.Schedulers;
34 |
35 | public class SearchCategoryFragment extends Fragment {
36 |
37 | private static final String TWEET_SEARCH_CATEGORY_KEY = "tweet-search-category";
38 | private final String LOG_TAG = SearchCategoryFragment.class.getName();
39 | private final String PARCELABLE_SEARCHED_TWEETS = "searched_tweets";
40 | private final String PARCELABLE_RECYCLER_VIEW_STATE = "recycler_view_state";
41 |
42 | @BindView(R.id.searched_tweet_container)
43 | RecyclerView recyclerView;
44 | @BindView(R.id.progress_bar)
45 | ProgressBar progressBar;
46 | @BindView(R.id.no_search_result_found)
47 | TextView noSearchResultFoundTextView;
48 | @BindView(R.id.network_error)
49 | TextView networkErrorTextView;
50 |
51 | private SearchCategoryAdapter mSearchCategoryAdapter;
52 |
53 | private String mSearchQuery;
54 | private String mTweetSearchCategory;
55 |
56 | public SearchCategoryFragment() {
57 | // Required empty public constructor
58 | }
59 |
60 | public static SearchCategoryFragment newInstance(String category, String query) {
61 | Bundle args = new Bundle();
62 | args.putString(Constants.TWEET_SEARCH_SUGGESTION_QUERY_KEY, query);
63 | args.putString(TWEET_SEARCH_CATEGORY_KEY, category);
64 | SearchCategoryFragment fragment = new SearchCategoryFragment();
65 | fragment.setArguments(args);
66 | return fragment;
67 | }
68 |
69 | @Override
70 | public void onCreate(@Nullable Bundle savedInstanceState) {
71 | super.onCreate(savedInstanceState);
72 | Bundle bundle = getArguments();
73 | if (bundle != null) {
74 | mTweetSearchCategory = bundle.getString(TWEET_SEARCH_CATEGORY_KEY);
75 | mSearchQuery = bundle.getString(Constants.TWEET_SEARCH_SUGGESTION_QUERY_KEY);
76 | }
77 | }
78 |
79 | @Override
80 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
81 | Bundle savedInstanceState) {
82 | // Inflate the layout for this fragment
83 | View view = inflater.inflate(R.layout.fragment_search_category, container, false);
84 | ButterKnife.bind(this, view);
85 |
86 | mSearchCategoryAdapter = new SearchCategoryAdapter(getActivity(), new ArrayList<>());
87 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
88 | if (savedInstanceState != null) {
89 | Parcelable recyclerViewState =
90 | savedInstanceState.getParcelable(PARCELABLE_RECYCLER_VIEW_STATE);
91 | recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
92 |
93 | List searchedTweets =
94 | savedInstanceState.getParcelableArrayList(PARCELABLE_SEARCHED_TWEETS);
95 | mSearchCategoryAdapter.setStatuses(searchedTweets);
96 | progressBar.setVisibility(View.GONE);
97 | recyclerView.setVisibility(View.VISIBLE);
98 | } else {
99 | fetchSearchedTweets();
100 | }
101 | recyclerView.setAdapter(mSearchCategoryAdapter);
102 |
103 | return view;
104 | }
105 |
106 | @Override
107 | public void onSaveInstanceState(Bundle outState) {
108 | super.onSaveInstanceState(outState);
109 | ArrayList searchedTweets = mSearchCategoryAdapter.getStatuses();
110 | outState.putParcelableArrayList(PARCELABLE_SEARCHED_TWEETS, searchedTweets);
111 |
112 | Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
113 | outState.putParcelable(PARCELABLE_RECYCLER_VIEW_STATE, recyclerViewState);
114 | }
115 |
116 | private void fetchSearchedTweets() {
117 | progressBar.setVisibility(View.VISIBLE);
118 | LoklakAPI loklakAPI = RestClient.createApi(LoklakAPI.class);
119 | loklakAPI.getSearchedTweets(mSearchQuery, mTweetSearchCategory, 30)
120 | .subscribeOn(Schedulers.io())
121 | .observeOn(AndroidSchedulers.mainThread())
122 | .subscribe(this::setSearchResultView, this::setNetworkErrorView);
123 | }
124 |
125 | private void setSearchResultView(Search search) {
126 | List statusList = search.getStatuses();
127 | networkErrorTextView.setVisibility(View.GONE);
128 | progressBar.setVisibility(View.GONE);
129 | if (statusList.size() == 0) {
130 | recyclerView.setVisibility(View.GONE);
131 |
132 | Resources res = getResources();
133 | String noSearchResultMessage = res.getString(R.string.no_search_match, mSearchQuery);
134 | noSearchResultFoundTextView.setVisibility(View.VISIBLE);
135 | noSearchResultFoundTextView.setText(noSearchResultMessage);
136 | } else {
137 | recyclerView.setVisibility(View.VISIBLE);
138 | mSearchCategoryAdapter.setStatuses(statusList);
139 | }
140 | }
141 |
142 | private void setNetworkErrorView(Throwable throwable) {
143 | Log.e(LOG_TAG, throwable.toString());
144 | progressBar.setVisibility(View.GONE);
145 | recyclerView.setVisibility(View.GONE);
146 | networkErrorTextView.setVisibility(View.VISIBLE);
147 | }
148 |
149 | @OnClick(R.id.network_error)
150 | public void setOnClickNetworkErrorTextViewListener() {
151 | networkErrorTextView.setVisibility(View.GONE);
152 | fetchSearchedTweets();
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/fragment/SearchFragment.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.fragment;
2 |
3 |
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.design.widget.TabLayout;
7 | import android.support.v4.app.Fragment;
8 | import android.support.v4.app.FragmentManager;
9 | import android.support.v4.view.ViewPager;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.EditText;
16 | import android.widget.ImageButton;
17 |
18 | import org.loklak.wok.adapters.SearchFragmentPagerAdapter;
19 | import org.loklak.wok.ui.suggestion.SuggestActivity;
20 | import org.loklak.wok.ui.activity.TweetPostingActivity;
21 | import org.loklak.wok.utility.Constants;
22 | import org.loklak.wok.R;
23 |
24 | import butterknife.BindView;
25 | import butterknife.ButterKnife;
26 | import butterknife.OnClick;
27 |
28 |
29 | public class SearchFragment extends Fragment {
30 |
31 | private final String LOG_TAG = SearchFragment.class.getName();
32 |
33 | @BindView(R.id.toolbar)
34 | Toolbar toolbar;
35 | @BindView(R.id.tweet_search_edit_text)
36 | EditText tweetSearchEditText;
37 | @BindView(R.id.clear_image_button)
38 | ImageButton clearImageButton;
39 | @BindView(R.id.tabs)
40 | TabLayout tabLayout;
41 | @BindView(R.id.view_pager)
42 | ViewPager viewPager;
43 |
44 | private String mQuery;
45 |
46 | public SearchFragment() {
47 | // Required empty public constructor
48 | }
49 |
50 | @Override
51 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
52 | Bundle savedInstanceState) {
53 | // Inflate the layout for this fragment
54 | View rootView = inflater.inflate(R.layout.fragment_search, container, false);
55 | ButterKnife.bind(this, rootView);
56 |
57 | AppCompatActivity appCompatActivity = (AppCompatActivity) getActivity();
58 | appCompatActivity.setSupportActionBar(toolbar);
59 | toolbar.setNavigationIcon(R.drawable.ic_arrow_back_24dp);
60 | toolbar.setNavigationOnClickListener(view -> getActivity().onBackPressed());
61 |
62 | Intent intent = getActivity().getIntent();
63 | mQuery = intent.getStringExtra(Constants.TWEET_SEARCH_SUGGESTION_QUERY_KEY);
64 | tweetSearchEditText.setOnFocusChangeListener((view, hasFocus) -> {
65 | if (hasFocus) {
66 | Intent suggestIntent = new Intent(getActivity(), SuggestActivity.class);
67 | startActivity(suggestIntent);
68 | getActivity().overridePendingTransition(R.anim.back_button_enter, R.anim.back_button_exit);
69 | }
70 | });
71 |
72 | tabLayout.setupWithViewPager(viewPager);
73 | setupViewPager(viewPager);
74 |
75 | return rootView;
76 | }
77 |
78 | @Override
79 | public void onStart() {
80 | super.onStart();
81 | tweetSearchEditText.setText(mQuery);
82 | }
83 |
84 | @OnClick(R.id.tweet_post)
85 | public void onClickFab() {
86 | Intent intent = new Intent(getActivity(), TweetPostingActivity.class);
87 | startActivity(intent);
88 | getActivity().overridePendingTransition(R.anim.bottom_enter, R.anim.top_exit);
89 | }
90 |
91 | private void setupViewPager(ViewPager viewPager) {
92 | FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
93 | SearchFragmentPagerAdapter pagerAdapter = new SearchFragmentPagerAdapter(fragmentManager);
94 | pagerAdapter.addFragment(SearchCategoryFragment.newInstance("", mQuery), "LATEST");
95 | pagerAdapter.addFragment(SearchCategoryFragment.newInstance("image", mQuery), "PHOTOS");
96 | pagerAdapter.addFragment(SearchCategoryFragment.newInstance("video", mQuery), "VIDEOS");
97 | viewPager.setAdapter(pagerAdapter);
98 | }
99 |
100 | @OnClick(R.id.clear_image_button)
101 | public void setOnClickClearImageButtonListener() {
102 | tweetSearchEditText.setText("");
103 | tweetSearchEditText.clearFocus();
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/suggestion/SuggestActivity.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.suggestion;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | import org.loklak.wok.R;
7 |
8 | public class SuggestActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_suggest);
14 | if (savedInstanceState == null) {
15 | SuggestFragment suggestFragment = new SuggestFragment();
16 | getSupportFragmentManager()
17 | .beginTransaction()
18 | .replace(R.id.fragment_container, suggestFragment)
19 | .commit();
20 | }
21 | }
22 |
23 | @Override
24 | public void onBackPressed() {
25 | super.onBackPressed();
26 | overridePendingTransition(R.anim.back_button_enter, R.anim.back_button_exit);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/suggestion/SuggestContract.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.suggestion;
2 |
3 | import org.loklak.wok.model.suggest.Query;
4 |
5 | import java.util.List;
6 |
7 | import io.reactivex.Observable;
8 |
9 |
10 | public interface SuggestContract {
11 |
12 | interface View {
13 |
14 | void showProgressBar(boolean show);
15 |
16 | void onSuggestionFetchSuccessful(List queries);
17 |
18 | void onSuggestionFetchError(Throwable throwable);
19 | }
20 |
21 | interface Presenter {
22 |
23 | void attachView(View view);
24 |
25 | void createCompositeDisposable();
26 |
27 | void loadSuggestionsFromAPI(String query, boolean showProgressBar);
28 |
29 | void loadSuggestionsFromDatabase();
30 |
31 | void saveSuggestions(List queries);
32 |
33 | void suggestionQueryChanged(Observable observable);
34 |
35 | void detachView();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/suggestion/SuggestFragment.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.suggestion;
2 |
3 |
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.os.Parcelable;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.app.Fragment;
9 | import android.support.v4.widget.SwipeRefreshLayout;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.LinearLayoutManager;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.support.v7.widget.Toolbar;
14 | import android.util.Log;
15 | import android.view.LayoutInflater;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.view.inputmethod.EditorInfo;
19 | import android.widget.EditText;
20 | import android.widget.ImageButton;
21 | import android.widget.Toast;
22 |
23 | import com.jakewharton.rxbinding2.widget.RxTextView;
24 |
25 | import org.loklak.wok.Constants;
26 | import org.loklak.wok.LoklakWokApplication;
27 | import org.loklak.wok.R;
28 | import org.loklak.wok.Utility;
29 | import org.loklak.wok.adapters.SuggestAdapter;
30 | import org.loklak.wok.model.suggest.Query;
31 | import org.loklak.wok.ui.activity.SearchActivity;
32 | import org.loklak.wok.ui.activity.TweetPostingActivity;
33 |
34 | import java.util.ArrayList;
35 | import java.util.List;
36 | import java.util.concurrent.TimeUnit;
37 |
38 | import javax.inject.Inject;
39 |
40 | import butterknife.BindString;
41 | import butterknife.BindView;
42 | import butterknife.ButterKnife;
43 | import butterknife.OnClick;
44 | import io.reactivex.Observable;
45 |
46 |
47 | public class SuggestFragment extends Fragment
48 | implements SuggestAdapter.OnSuggestionClickListener, SuggestContract.View{
49 |
50 | private final String PARCELABLE_RECYCLER_VIEW_STATE = "recycler_view_state";
51 |
52 | @BindView(R.id.toolbar)
53 | Toolbar toolbar;
54 |
55 | @BindView(R.id.tweet_search_edit_text)
56 | EditText tweetSearchEditText;
57 |
58 | @BindView(R.id.clear_image_button)
59 | ImageButton clearImageButton;
60 |
61 | @BindView(R.id.refresh_suggestions)
62 | SwipeRefreshLayout refreshSuggestions;
63 |
64 | @BindView(R.id.tweet_search_suggestions)
65 | RecyclerView tweetSearchSuggestions;
66 |
67 | @BindString(R.string.network_request_error)
68 | String networkRequestError;
69 |
70 | @Inject
71 | SuggestPresenter suggestPresenter;
72 |
73 | private Toast mToast;
74 |
75 | private SuggestAdapter mSuggestAdapter;
76 |
77 | private final String LOG_TAG = SuggestFragment.class.getName();
78 |
79 | @Override
80 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
81 | Bundle savedInstanceState) {
82 | // Inflate the layout for this fragment
83 | View rootView = inflater.inflate(R.layout.fragment_suggest, container, false);
84 | ButterKnife.bind(this, rootView);
85 |
86 | LoklakWokApplication application = (LoklakWokApplication) getActivity().getApplication();
87 | application.getApplicationComponent().inject(this);
88 | suggestPresenter.attachView(this);
89 |
90 | return rootView;
91 | }
92 |
93 | @Override
94 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
95 | AppCompatActivity activity = (AppCompatActivity) getActivity();
96 | activity.setSupportActionBar(toolbar);
97 | toolbar.setNavigationIcon(R.drawable.ic_arrow_back_24dp);
98 | toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed());
99 |
100 | tweetSearchEditText.setOnEditorActionListener((textView, actionId, event) -> {
101 | if (actionId == EditorInfo.IME_ACTION_SEARCH) {
102 | String searchQuery = tweetSearchEditText.getText().toString();
103 | startSearchActivity(searchQuery);
104 | return true;
105 | }
106 | return false;
107 | });
108 |
109 | refreshSuggestions.setOnRefreshListener(() -> {
110 | String query = tweetSearchEditText.getText().toString();
111 | suggestPresenter.loadSuggestionsFromAPI(query, true);
112 | });
113 |
114 | mSuggestAdapter = new SuggestAdapter(new ArrayList<>(), this);
115 | tweetSearchSuggestions.setLayoutManager(new LinearLayoutManager(getActivity()));
116 | tweetSearchSuggestions.setAdapter(mSuggestAdapter);
117 |
118 | suggestPresenter.loadSuggestionsFromDatabase();
119 |
120 | if (savedInstanceState != null) {
121 | Parcelable recyclerViewState =
122 | savedInstanceState.getParcelable(PARCELABLE_RECYCLER_VIEW_STATE);
123 | tweetSearchSuggestions.getLayoutManager().onRestoreInstanceState(recyclerViewState);
124 | } else {
125 | suggestPresenter.loadSuggestionsFromAPI("", true);
126 | }
127 | }
128 |
129 | @Override
130 | public void onStart() {
131 | super.onStart();
132 | suggestPresenter.createCompositeDisposable();
133 | Observable observable
134 | = RxTextView.textChanges(tweetSearchEditText).debounce(400, TimeUnit.MILLISECONDS);
135 | suggestPresenter.suggestionQueryChanged(observable);
136 | }
137 |
138 | private void startSearchActivity(String searchQuery) {
139 | Intent intent = new Intent(getActivity(), SearchActivity.class);
140 | intent.putExtra(Constants.TWEET_SEARCH_SUGGESTION_QUERY_KEY, searchQuery);
141 | startActivity(intent);
142 | getActivity().overridePendingTransition(R.anim.enter, R.anim.exit);
143 | }
144 |
145 | @OnClick(R.id.clear_image_button)
146 | public void onClickedClearImageButton() {
147 | tweetSearchEditText.setText("");
148 | }
149 |
150 | @Override
151 | public void onSaveInstanceState(Bundle outState) {
152 | super.onSaveInstanceState(outState);
153 | Parcelable recyclerViewState =
154 | tweetSearchSuggestions.getLayoutManager().onSaveInstanceState();
155 | outState.putParcelable(PARCELABLE_RECYCLER_VIEW_STATE, recyclerViewState);
156 | }
157 |
158 | @Override
159 | public void onStop() {
160 | super.onStop();
161 | List queries = mSuggestAdapter.getQueries();
162 | suggestPresenter.saveSuggestions(queries);
163 | suggestPresenter.detachView();
164 | }
165 |
166 | @Override
167 | public void onSuggestionClicked(Query query) {
168 | String searchQuery = query.getQuery();
169 | startSearchActivity(searchQuery);
170 | }
171 |
172 | @OnClick(R.id.tweet_post)
173 | public void onClickFab() {
174 | Intent intent = new Intent(getActivity(), TweetPostingActivity.class);
175 | startActivity(intent);
176 | getActivity().overridePendingTransition(R.anim.bottom_enter, R.anim.top_exit);
177 | }
178 |
179 | @Override
180 | public void onSuggestionFetchSuccessful(List queries) {
181 | if (queries != null) {
182 | mSuggestAdapter.setQueries(queries);
183 | }
184 | }
185 |
186 | @Override
187 | public void showProgressBar(boolean show) {
188 | if (show) {
189 | refreshSuggestions.setRefreshing(true);
190 | tweetSearchSuggestions.setVisibility(View.INVISIBLE);
191 | } else {
192 | refreshSuggestions.setRefreshing(false);
193 | tweetSearchSuggestions.setVisibility(View.VISIBLE);
194 | }
195 | }
196 |
197 | @Override
198 | public void onSuggestionFetchError(Throwable throwable) {
199 | Log.e(LOG_TAG, throwable.toString());
200 | Utility.displayToast(mToast, getActivity(), networkRequestError);
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/ui/suggestion/SuggestPresenter.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.ui.suggestion;
2 |
3 |
4 | import org.loklak.wok.api.loklak.LoklakAPI;
5 | import org.loklak.wok.model.suggest.Query;
6 |
7 | import java.util.List;
8 |
9 | import javax.inject.Inject;
10 |
11 | import io.reactivex.Observable;
12 | import io.reactivex.android.schedulers.AndroidSchedulers;
13 | import io.reactivex.disposables.CompositeDisposable;
14 | import io.reactivex.schedulers.Schedulers;
15 | import io.realm.Realm;
16 | import io.realm.RealmResults;
17 |
18 | public class SuggestPresenter implements SuggestContract.Presenter {
19 |
20 | private final Realm mRealm;
21 | private LoklakAPI mLoklakAPI;
22 | private SuggestContract.View mView;
23 | private CompositeDisposable mCompositeDisposable;
24 |
25 | @Inject
26 | public SuggestPresenter(LoklakAPI loklakAPI, Realm realm) {
27 | this.mLoklakAPI = loklakAPI;
28 | this.mRealm = realm;
29 | mCompositeDisposable = new CompositeDisposable();
30 | }
31 |
32 | @Override
33 | public void attachView(SuggestContract.View view) {
34 | this.mView = view;
35 | }
36 |
37 | @Override
38 | public void createCompositeDisposable() {
39 | if (mCompositeDisposable == null ) {
40 | mCompositeDisposable = new CompositeDisposable();
41 | }
42 | }
43 |
44 | @Override
45 | public void loadSuggestionsFromAPI(String query, boolean showProgressBar) {
46 | mView.showProgressBar(showProgressBar);
47 | mCompositeDisposable.add(mLoklakAPI.getSuggestions(query)
48 | .flatMap(suggestData -> Observable.just(suggestData.getQueries()))
49 | .subscribeOn(Schedulers.io())
50 | .observeOn(AndroidSchedulers.mainThread())
51 | .subscribe(
52 | mView::onSuggestionFetchSuccessful,
53 | throwable -> {
54 | mView.showProgressBar(false);
55 | mView.onSuggestionFetchError(throwable);
56 | },
57 | () -> mView.showProgressBar(false)
58 | )
59 | );
60 | }
61 |
62 | @Override
63 | public void loadSuggestionsFromDatabase() {
64 | RealmResults realmResults = mRealm.where(Query.class).findAll();
65 | List queries = mRealm.copyFromRealm(realmResults);
66 | mView.onSuggestionFetchSuccessful(queries);
67 | }
68 |
69 | @Override
70 | public void saveSuggestions(List queries) {
71 | mRealm.beginTransaction();
72 | mRealm.where(Query.class).findAll().deleteAllFromRealm();
73 | mRealm.copyToRealm(queries);
74 | mRealm.commitTransaction();
75 | }
76 |
77 | @Override
78 | public void suggestionQueryChanged(Observable observable) {
79 | mCompositeDisposable.add(observable
80 | .observeOn(AndroidSchedulers.mainThread())
81 | .subscribe(charSequence -> {
82 | if (charSequence.length() > 0) {
83 | loadSuggestionsFromAPI(charSequence.toString(), false);
84 | }
85 | })
86 | );
87 | }
88 |
89 | @Override
90 | public void detachView() {
91 | if (mCompositeDisposable != null && !mCompositeDisposable.isDisposed()) {
92 | mCompositeDisposable.dispose();
93 | mCompositeDisposable = null;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/utility/Constants.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.utility;
2 |
3 |
4 | import android.view.View;
5 |
6 | import butterknife.ButterKnife.Action;
7 |
8 | public class Constants {
9 |
10 | public static final String BASE_URL_LOKLAK = "https://api.loklak.org/";
11 |
12 | public static final String KEY = "";
13 | public static final String SECRET = "";
14 |
15 | public static final String TWEET_SEARCH_SUGGESTION_QUERY_KEY = "search_suggestion_query";
16 | public static final String OAUTH_ACCESS_TOKEN_KEY = "oauth_access_token";
17 | public static final String OAUTH_ACCESS_TOKEN_SECRET_KEY = "oauth_access_token_secret";
18 | public static final String USER_ID = "user_id";
19 | public static final String USER_NAME = "user_name";
20 | public static final String USER_SCREEN_NAME = "user_screen_name";
21 | public static final String USER_PROFILE_IMAGE_URL = "user_profile_image_url";
22 |
23 | public static final Action VISIBLE = (view, index) -> view.setVisibility(View.VISIBLE);
24 | public static final Action GONE = (view, index) -> view.setVisibility(View.GONE);
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/utility/FileUtils.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.utility;
2 |
3 | import android.content.ContentUris;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 | import android.os.Build;
8 | import android.os.Environment;
9 | import android.provider.DocumentsContract;
10 | import android.provider.MediaStore;
11 |
12 |
13 | public class FileUtils {
14 |
15 | final static boolean IS_KITKAT_AND_ABOVE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
16 |
17 | public static boolean isExternalStorageDocument(Uri uri) {
18 | return "com.android.externalstorage.documents".equals(uri.getAuthority());
19 | }
20 |
21 | public static boolean isDownloadsDocument(Uri uri) {
22 | return "com.android.providers.downloads.documents".equals(uri.getAuthority());
23 | }
24 |
25 | public static boolean isMediaDocument(Uri uri) {
26 | return "com.android.providers.media.documents".equals(uri.getAuthority());
27 | }
28 |
29 | public static String getColumnData(Context context, Uri uri,
30 | String selection, String[] selectionArgs) {
31 | final String column = "_data";
32 | final String[] projection = {column};
33 |
34 | Cursor cursor = context.getContentResolver().query(
35 | uri,
36 | projection,
37 | selection,
38 | selectionArgs,
39 | null);
40 | if (cursor != null && cursor.moveToFirst()) {
41 | final int index = cursor.getColumnIndex(column);
42 | if (index >= 0) {
43 | return cursor.getString(index);
44 | }
45 | }
46 | if (cursor != null) {
47 | cursor.close();
48 | }
49 | return null;
50 | }
51 |
52 | public static String getPath(final Context context, final Uri uri) {
53 | if (IS_KITKAT_AND_ABOVE && DocumentsContract.isDocumentUri(context, uri)) {
54 |
55 | // document provider
56 | if (isExternalStorageDocument(uri)) {
57 | final String docId = DocumentsContract.getDocumentId(uri);
58 | final String[] split = docId.split(":");
59 | final String type = split[0];
60 | final String id = split[1];
61 |
62 | if ("primary".equalsIgnoreCase(type)) {
63 | return Environment.getExternalStorageDirectory() + "/" + id;
64 | }
65 | }
66 | // downloads provider
67 | else if (isDownloadsDocument(uri)) {
68 | final String id = DocumentsContract.getDocumentId(uri);
69 | final Uri contentUri = ContentUris.withAppendedId(
70 | Uri.parse("content://downloads/public_downloads"),
71 | Long.parseLong(id));
72 | return getColumnData(context, contentUri, null, null);
73 | }
74 | // media provider
75 | else if (isMediaDocument(uri)) {
76 | Uri contentUri = null;
77 | final String docId = DocumentsContract.getDocumentId(uri);
78 | final String[] split = docId.split(":");
79 | final String type = split[0];
80 | final String id = split[1];
81 |
82 | if (type.equals("image")) {
83 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
84 | }
85 | else if (type.equals("video")) {
86 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
87 | }
88 | else if (type.equals("audio")) {
89 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
90 | }
91 |
92 | final String selection = "_id=?";
93 | final String[] selectionArgs = {id};
94 | return getColumnData(context, contentUri, selection, selectionArgs);
95 | }
96 | // file
97 | else if (uri.getScheme().equalsIgnoreCase("file")) return uri.getPath();
98 | }
99 | return null;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/org/loklak/wok/utility/SharedPrefUtil.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok.utility;
2 |
3 |
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.preference.PreferenceManager;
7 |
8 | public class SharedPrefUtil {
9 |
10 | public static String getSharedPrefString(Context context, String key) {
11 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
12 | return sharedPref.getString(key, "");
13 | }
14 |
15 | public static void setSharedPrefString(Context context, String key, String value) {
16 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
17 | SharedPreferences.Editor editor = sharedPref.edit();
18 | editor.putString(key, value);
19 | editor.apply();
20 | }
21 |
22 | public static void removeSharedPrefKey(Context context, String key) {
23 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
24 | SharedPreferences.Editor editor = sharedPref.edit();
25 | editor.remove(key);
26 | editor.commit();
27 | }
28 |
29 | public static void clearSharedPrefData(Context context) {
30 | PreferenceManager.getDefaultSharedPreferences(context).edit().clear().apply();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/back_button_bottom_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/back_button_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/back_button_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/back_button_top_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/bottom_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/top_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/heart.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_location_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_gallery_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location_on_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/loklak_splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/app/src/main/res/drawable/loklak_splash.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/media_button_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reply.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/retweet.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_round_corner_tweet_media_remove.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_round_corner_tweet_post.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tweet_button_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/twitter_pink.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/twitter_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_suggest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tweet_harvesting.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tweet_posting.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_tweet_harvesting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
22 |
32 |
33 |
41 |
42 |
49 |
50 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fab_tweet_posting.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
22 |
30 |
31 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
55 |
56 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search_category.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
19 |
20 |
33 |
34 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_suggest.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
17 |
18 |
21 |
22 |
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_tweet_harvesting.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_tweet_posting.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
16 |
17 |
18 |
19 |
27 |
28 |
38 |
39 |
45 |
46 |
47 |
55 |
56 |
57 |
58 |
64 |
65 |
72 |
73 |
82 |
83 |
92 |
93 |
102 |
103 |
111 |
112 |
122 |
123 |
124 |
125 |
129 |
130 |
141 |
142 |
151 |
152 |
157 |
158 |
159 |
160 |
161 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/grid_elem_tweet_photo.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_post_tweet_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_harvested_tweet.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
32 |
33 |
41 |
42 |
43 |
44 |
51 |
52 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_tweet_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
22 |
23 |
28 |
29 |
33 |
34 |
43 |
44 |
52 |
53 |
59 |
60 |
61 |
62 |
69 |
70 |
75 |
76 |
81 |
82 |
87 |
88 |
92 |
93 |
94 |
95 |
100 |
101 |
105 |
106 |
113 |
114 |
115 |
116 |
121 |
122 |
126 |
127 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_tweet_search_suggest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_tweet_harvesting.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_tweet_posting.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/app/src/main/res/mipmap-hdpi/launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/app/src/main/res/mipmap-mdpi/launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/app/src/main/res/mipmap-xhdpi/launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/app/src/main/res/mipmap-xxhdpi/launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/app/src/main/res/mipmap-xxxhdpi/launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 | #C2185B
5 | #E91E63
6 | #F48FB1
7 | #757575
8 |
9 |
10 | #BDBDBD
11 |
12 |
13 | #BDBDBD
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1dp
4 | 8dp
5 | 4dp
6 | 8dp
7 | 24sp
8 | 24dp
9 | 240dp
10 | 48dp
11 | 16dp
12 |
13 |
14 | 20sp
15 | 24dp
16 |
17 |
18 | 8dp
19 | 40sp
20 |
21 |
22 | 48dp
23 |
24 |
25 | 16sp
26 | 16sp
27 | 16sp
28 | 4dp
29 | 8dp
30 | 40dp
31 | 24dp
32 | 16sp
33 | 8dp
34 |
35 |
36 | 280dp
37 | 24dp
38 | 8dp
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Loklak Wok
3 | Please check your internet connection. Tap here to try again!
4 | Search Twitter
5 | Clears Search EditText for a new search
6 |
7 |
8 | Cannot fetch suggestions, Try Again!
9 |
10 |
11 | 0
12 | TWEETS HARVESTED
13 |
14 |
15 | No results for \"%1$s\"
16 |
17 |
18 |
19 | What\'s happening?
20 | Capture photo to attach in tweet
21 | Attach images from gallery in tweet
22 | Share your location with tweet
23 | Tweet
24 | 140
25 | %1$f\u00B0 N, %2$f\u00B0 E
26 |
27 | Something went wrong. Please try again!
28 | Allow Loklak Wok to post your tweets.
29 | Allow Loklak Wok
30 | At most 4 images allowed!
31 | Tweet can\'t be empty!
32 | Tweet successfully posted
33 | Tweet couldn\'t be posted. Please try again!
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/test/java/org/loklak/wok/TwitterOAuthInterceptorTest.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok;
2 |
3 | /*
4 | * Copyright (C) 2015 Jake Wharton
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import org.assertj.core.api.Assertions;
20 | import org.junit.Before;
21 | import org.junit.Test;
22 | import org.loklak.wok.api.twitter.TwitterOAuthInterceptor;
23 | import org.mockito.Mockito;
24 |
25 | import java.io.IOException;
26 | import java.util.Random;
27 |
28 | import okhttp3.FormBody;
29 | import okhttp3.Request;
30 | import okhttp3.RequestBody;
31 | import okio.ByteString;
32 |
33 | public final class TwitterOAuthInterceptorTest {
34 | TwitterOAuthInterceptor oauth1;
35 |
36 | @Before public void setUp() throws IOException {
37 | // Data from https://dev.twitter.com/oauth/overview/authorizing-requests.
38 | // Tested via http://www.oauth-signatur.de/en
39 |
40 | Random notRandom = new Random() {
41 | @Override public void nextBytes(byte[] bytes) {
42 | if (bytes.length != 32) throw new AssertionError();
43 | ByteString hex = ByteString.decodeBase64("kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4c+g");
44 | byte[] nonce = hex.toByteArray();
45 | System.arraycopy(nonce, 0, bytes, 0, nonce.length);
46 | }
47 | };
48 |
49 | // Mock the time :)
50 | TwitterOAuthInterceptor.Clock clock = Mockito.mock(TwitterOAuthInterceptor.Clock.class);
51 | Mockito.when(clock.millis()).thenReturn("1318622958");
52 |
53 | oauth1 = new TwitterOAuthInterceptor.Builder()
54 | .consumerKey("xvz1evFS4wEEPTGEFPHBog")
55 | .consumerSecret("kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw")
56 | .accessToken("370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb")
57 | .accessSecret("LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE")
58 | .random(notRandom)
59 | .clock(clock)
60 | .build();
61 | }
62 |
63 | @org.junit.Test
64 | public void testExample() {
65 |
66 | }
67 |
68 | @Test public void withBody() throws IOException {
69 | RequestBody body = new FormBody.Builder() //
70 | .add("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
71 | .build();
72 | Request request = new Request.Builder() //
73 | .url("https://api.twitter.com/1/statuses/update.json?include_entities=true")
74 | .post(body)
75 | .build();
76 |
77 | Request signed = oauth1.signRequest(request);
78 | assertAuthHeader(signed, "tnnArxj06cWHq44gCs1OSKk%2FjLY%3D");
79 | }
80 |
81 | @Test public void noBody() throws IOException {
82 | Request request = new Request.Builder() //
83 | .url("https://api.twitter.com/1.1/statuses/mentions_timeline.json?count=100&include_entities=false")
84 | .build();
85 |
86 | Request signed = oauth1.signRequest(request);
87 | assertAuthHeader(signed, "hn5jxegoQxNM6SXvQgVhK15yQL8%3D");
88 | }
89 |
90 | @Test public void urlSameQueryParams() throws IOException {
91 | Request request = new Request.Builder() //
92 | .url("https://api.twitter.com/1.1/statuses/home_timeline.json?since_id=12&since_id=13")
93 | .build();
94 |
95 | Request signed = oauth1.signRequest(request);
96 | assertAuthHeader(signed, "R8m%2BYY%2FZG5GJ%2F%2F3zCrE65DkTdCk%3D");
97 | }
98 |
99 | /**
100 | * Asserts that the provided request contains an expected header, with provided oauth signature.
101 | */
102 | private static void assertAuthHeader(Request request, String signature) {
103 | Assertions.assertThat(request.header("Authorization")).isEqualTo("OAuth "
104 | + "oauth_consumer_key=\"xvz1evFS4wEEPTGEFPHBog\", "
105 | + "oauth_nonce=\"kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg\", "
106 | + "oauth_signature=\"" + signature + "\", "
107 | + "oauth_signature_method=\"HMAC-SHA1\", "
108 | + "oauth_timestamp=\"1318622958\", "
109 | + "oauth_token=\"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb\", "
110 | + "oauth_version=\"1.0\"");
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/test/java/org/loklak/wok/UrlEscapeUtilsTest.java:
--------------------------------------------------------------------------------
1 | package org.loklak.wok;
2 |
3 | /*
4 | * Copyright (C) 2009 The Guava Authors
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import org.junit.Test;
20 | import org.loklak.wok.api.twitter.UrlEscapeUtils;
21 |
22 | import static org.junit.Assert.assertEquals;
23 | import static org.junit.Assert.assertNotNull;
24 | import static org.junit.Assert.assertNull;
25 | import static org.junit.Assert.fail;
26 |
27 | /**
28 | * Tests for the {@link UrlEscapeUtils} class.
29 | *
30 | * Note: This is a ripoff of Guava's actual {@code UtlEscapersTest}.
31 | *
32 | * @author David Beaumont
33 | * @author Sven Mawson
34 | */
35 | public class UrlEscapeUtilsTest {
36 | @Test public void actsAsUrlFormParameterEscaper() {
37 | try {
38 | UrlEscapeUtils.escape(null);
39 | fail("Escaping null string should throw exception");
40 | } catch (NullPointerException x) {
41 | // pass
42 | }
43 |
44 | // 0-9, A-Z, a-z should be left unescaped
45 | assertUnescaped('a');
46 | assertUnescaped('z');
47 | assertUnescaped('A');
48 | assertUnescaped('Z');
49 | assertUnescaped('0');
50 | assertUnescaped('9');
51 |
52 | // Unreserved characters used in java.net.URLEncoder
53 | assertUnescaped('-');
54 | assertUnescaped('_');
55 | assertUnescaped('.');
56 | assertUnescaped('*');
57 |
58 | assertEscaping("%3D", '=');
59 | assertEscaping("%00", '\u0000'); // nul
60 | assertEscaping("%7F", '\u007f'); // del
61 | assertEscaping("%C2%80", '\u0080'); // xx-00010,x-000000
62 | assertEscaping("%DF%BF", '\u07ff'); // xx-11111,x-111111
63 | assertEscaping("%E0%A0%80", '\u0800'); // xxx-0000,x-100000,x-00,0000
64 | assertEscaping("%EF%BF%BF", '\uffff'); // xxx-1111,x-111111,x-11,1111
65 | assertUnicodeEscaping("%F0%90%80%80", '\uD800', '\uDC00');
66 | assertUnicodeEscaping("%F4%8F%BF%BF", '\uDBFF', '\uDFFF');
67 |
68 | assertEquals("", UrlEscapeUtils.escape(""));
69 | assertEquals("safestring", UrlEscapeUtils.escape("safestring"));
70 | assertEquals("embedded%00null", UrlEscapeUtils.escape("embedded\0null"));
71 | assertEquals("max%EF%BF%BFchar", UrlEscapeUtils.escape("max\uffffchar"));
72 |
73 | // Specified as safe by RFC 2396 but not by java.net.URLEncoder.
74 | assertEscaping("%21", '!');
75 | assertEscaping("%28", '(');
76 | assertEscaping("%29", ')');
77 | assertEscaping("%7E", '~');
78 | assertEscaping("%27", '\'');
79 |
80 | // Plus for spaces
81 | assertEscaping("+", ' ');
82 | assertEscaping("%2B", '+');
83 |
84 | assertEquals("safe+with+spaces", UrlEscapeUtils.escape("safe with spaces"));
85 | assertEquals("foo%40bar.com", UrlEscapeUtils.escape("foo@bar.com"));
86 | }
87 |
88 | /**
89 | * Asserts that {@link UrlEscapeUtils} escapes the given character.
90 | *
91 | * @param expected the expected escape result
92 | * @param c the character to test
93 | */
94 | private static void assertEscaping(String expected, char c) {
95 | String escaped = computeReplacement(c);
96 | assertNotNull(escaped);
97 | assertEquals(expected, escaped);
98 | }
99 |
100 | /**
101 | * Asserts that {@link UrlEscapeUtils} does not escape the given character.
102 | *
103 | * @param c the character to test
104 | */
105 | private static void assertUnescaped(char c) {
106 | assertNull(computeReplacement(c));
107 | }
108 |
109 | /**
110 | * Asserts that {@link UrlEscapeUtils} escapes the given hi/lo surrogate pair into
111 | * the expected string.
112 | *
113 | * @param expected the expected output string
114 | * @param hi the high surrogate pair character
115 | * @param lo the low surrogate pair character
116 | */
117 | private static void assertUnicodeEscaping(String expected, char hi, char lo) {
118 | int cp = Character.toCodePoint(hi, lo);
119 | String escaped = computeReplacement(cp);
120 | assertNotNull(escaped);
121 | assertEquals(expected, escaped);
122 | }
123 |
124 | private static String computeReplacement(char c) {
125 | return stringOrNull(UrlEscapeUtils.escape(c));
126 | }
127 |
128 | private static String computeReplacement(int cp) {
129 | return stringOrNull(UrlEscapeUtils.escape(cp));
130 | }
131 |
132 | private static String stringOrNull(char[] chars) {
133 | return (chars == null) ? null : new String(chars);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven { url 'https://maven.fabric.io/public' }
7 | google()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.1'
11 | classpath 'me.tatarka:gradle-retrolambda:3.2.0'
12 | classpath "io.realm:realm-gradle-plugin:3.3.1"
13 | classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
14 | classpath 'io.fabric.tools:gradle:1.+'
15 |
16 | // NOTE: Do not place your application dependencies here; they belong
17 | // in the individual module build.gradle files
18 | }
19 | }
20 |
21 | allprojects {
22 | repositories {
23 | jcenter()
24 | maven { url 'https://jitpack.io' }
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/docs/_static/tweet_harvesting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/docs/_static/tweet_harvesting.png
--------------------------------------------------------------------------------
/docs/_static/tweet_post_authorization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/docs/_static/tweet_post_authorization.png
--------------------------------------------------------------------------------
/docs/_static/tweet_posting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/docs/_static/tweet_posting.png
--------------------------------------------------------------------------------
/docs/_static/tweet_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/docs/_static/tweet_search.png
--------------------------------------------------------------------------------
/docs/_static/tweet_search_suggestions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/docs/_static/tweet_search_suggestions.png
--------------------------------------------------------------------------------
/docs/_static/twitter_authorization_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/docs/_static/twitter_authorization_login.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 11 19:54:48 IST 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.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/scripts/prep-key.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | export DEPLOY_BRANCH=${DEPLOY_BRANCH:-master}
5 |
6 | if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "fossasia/loklak_wok_android" -o "$TRAVIS_BRANCH" != "$DEPLOY_BRANCH" ]; then
7 | echo "We decrypt key only for pushes to the master branch and not PRs. So, skip."
8 | exit 0
9 | fi
10 |
11 | openssl aes-256-cbc -K $encrypted_342429ced49b_key -iv $encrypted_342429ced49b_iv -in ./scripts/secrets.tar.enc -out ./scripts/secrets.tar -d
12 | tar xvf ./scripts/secrets.tar -C scripts/
13 |
--------------------------------------------------------------------------------
/scripts/secrets.tar.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/loklak_wok_android/a22f75c76f2457aae5eba32f1c1b95d77f458be6/scripts/secrets.tar.enc
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/update-apk.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | export PUBLISH_BRANCH=${PUBLISH_BRANCH:-master}
4 |
5 | # Signing Apps
6 | if [ "$TRAVIS_BRANCH" == "$PUBLISH_BRANCH" ]; then
7 | echo "Push to master branch detected, signing the app..."
8 | cp ./app/build/outputs/apk/app-release-unsigned.apk ./app/build/outputs/apk/app-release-unaligned.apk
9 | jarsigner -verbose -tsa http://timestamp.comodoca.com/rfc3161 -sigalg SHA1withRSA -digestalg SHA1 -keystore scripts/key.jks -storepass $STORE_PASS -keypass $KEY_PASS ./app/build/outputs/apk/app-release-unaligned.apk $ALIAS
10 | ${ANDROID_HOME}/build-tools/25.0.3/zipalign -v -p 4 ./app/build/outputs/apk/app-release-unaligned.apk ./app/build/outputs/apk/app-release.apk
11 |
12 | # Publishing App
13 | echo "Publishing app to Play Store..."
14 | gem install fastlane
15 | fastlane supply --apk ./app/build/outputs/apk/app-release.apk --track alpha --json_key scripts/fastlane.json --package_name $PACKAGE_NAME
16 | fi
17 |
18 | # Create a new folder and copy the builded apk to there
19 | mkdir $HOME/work_new
20 | cp -rf ./app/build/outputs/apk/ $HOME/work_new
21 |
22 | git config --global user.email "no-reply@travis-ci.org"
23 | git config --global user.name "apt-bot"
24 |
25 | # Clone the repo on apk branch after going to $HOME
26 | cd $HOME
27 | git clone --quiet --branch=apk https://apt-bot:$BOT_TOKEN@github.com/fossasia/loklak_wok_android apks > /dev/null
28 |
29 | # Switch to the repository and checkout to an orphan branch
30 | cd apks
31 | git checkout --orphan temp
32 |
33 | # Delete the previous apks and copy new apks
34 | rm -rf ./work_new
35 | cp -rf $HOME/work_new/ ./
36 |
37 | # Add and commit the files
38 | git add $HOME/work_new/apk/*.apk
39 | git commit --message "Apk update for build:$TRAVIS_BUILD_NUMBER | Generated by Travis CI --skip-ci"
40 |
41 | # Delete the previous branch and rename the new branch
42 | git branch -D apk
43 | git branch -m apk
44 |
45 | # Push to the branch apk
46 | git push origin apk --force --quiet> /dev/null
47 |
--------------------------------------------------------------------------------