diff --git a/.github/workflows/unitTest.yml b/.github/workflows/unitTest.yml new file mode 100644 index 00000000..9e1e07c2 --- /dev/null +++ b/.github/workflows/unitTest.yml @@ -0,0 +1,38 @@ +name: Hanji Unit Testing + +on: + push: + branches: + - master + pull_request: + branches: + - master + - 2.0 + +jobs: + run_tests: + + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Create keys.txt + env: + KEYS_TXT: ${{ secrets.KEYS_TXT }} + run: echo $KEYS_TXT > keys.txt + - name: Create google-services.json + env: + FIREBASE_SECRET: ${{ secrets.GOOGLE_SERVICES }} + run: echo $FIREBASE_SECRET > app/google-services.json + - name: Run unit tests + run: ./gradlew testDebugUnitTest --stacktrace + - name: Test Report + uses: asadmansr/android-test-report-action@v1.2.0 + if: ${{ always() }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index bb21f0b5..602a2c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /local.properties /.idea/workspace.xml /.idea/libraries +/.idea .DS_Store /build /captures diff --git a/app/build.gradle b/app/build.gradle index 4918c8b2..71746c96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,71 +1,142 @@ apply plugin: 'com.android.application' -apply plugin: 'io.fabric' +apply plugin: 'jacoco' + +jacoco { + toolVersion = '0.8.2' +} + +tasks.withType(Test) { + jacoco.includeNoLocationClasses = true +} + +// Our merge report task +task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) { + + reports { + xml.enabled = true + html.enabled = true + } + + def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] + def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/debug", excludes: fileFilter) + def mainSrc = "$project.projectDir/src/main/java" + + sourceDirectories.from = files([mainSrc]) + classDirectories.from = files([debugTree]) + executionData.from = fileTree(dir: project.buildDir, includes: [ + 'jacoco/testDebugUnitTest.exec', 'outputs/code_coverage/debugAndroidTest/connected/**/*.ec' + ]) +} android { - compileSdkVersion 27 + compileSdkVersion 29 defaultConfig { applicationId "com.a494studios.koreanconjugator" minSdkVersion 19 - targetSdkVersion 27 + targetSdkVersion 29 multiDexEnabled true - versionCode 13 - versionName "1.4.1" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + versionCode 21 + versionName "2.0.4" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug { + testCoverageEnabled = true + } } buildTypes.each { it.buildConfigField("String", "SERVER_URL", getApiKey("serverURL")) it.buildConfigField("String", "ADMOB_KEY", getApiKey("adMobKey")) it.buildConfigField("String", "SLACK_KEY", getApiKey("slackKey")) - it.resValue("string", "DISPLAY_AD_ID" , getApiKey("displayAdID")) - it.resValue("string", "MAIN_AD_ID" , getApiKey("mainAdID")) - it.resValue("string", "SEARCH_AD_ID" , getApiKey("searchAdID")) - it.resValue("string", "SEARCH_RESULTS_AD_ID" , getApiKey("searchResultsAdID")) + it.resValue("string", "DISPLAY_AD_ID", getApiKey("displayAdID")) + it.resValue("string", "MAIN_AD_ID", getApiKey("mainAdID")) + it.resValue("string", "SEARCH_AD_ID", getApiKey("searchAdID")) + it.resValue("string", "SEARCH_RESULTS_AD_ID", getApiKey("searchResultsAdID")) + it.resValue("string", "CONJ_INFO_AD_ID", getApiKey("conjInfoAdID")) } configurations.all { resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' } + + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + animationsDisabled true + unitTests { + includeAndroidResources = true + } + } + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } } //return a MY API KEY from a properties file. -def getApiKey(String property){ +def getApiKey(String property) { Properties properties = new Properties() properties.load(new FileInputStream("keys.txt")) - return "\"" + properties.getProperty(property) +"\"" + return "\"" + properties.getProperty(property) + "\"" } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.android.support:support-v4:27.1.1' - implementation 'com.android.support:multidex:1.0.3' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' - implementation 'com.android.volley:volley:1.0.0' - implementation 'com.android.support:cardview-v7:27.1.1' - implementation 'com.android.support:design:27.1.1' - implementation 'com.google.firebase:firebase-core:11.8.0' - implementation 'com.google.firebase:firebase-ads:11.8.0' - implementation 'com.google.firebase:firebase-messaging:11.8.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { transitive = true } + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.cardview:cardview:1.0.0' + implementation "androidx.recyclerview:recyclerview:1.1.0" + implementation 'com.google.android.material:material:1.2.1' + implementation 'org.jetbrains:annotations:16.0.2' implementation 'com.github.frankiesardo:linearlistview:1.0.1@aar' - implementation 'com.google.code.gson:gson:2.8.2' - implementation 'com.google.guava:guava:23.0-android' - implementation 'com.andkulikov:transitionseverywhere:1.7.8' + implementation 'com.codemybrainsout.onboarding:onboarder:1.0.4' + + // Firebase + implementation 'com.google.firebase:firebase-core:17.4.3' + implementation 'com.google.firebase:firebase-ads:19.2.0' + implementation 'com.google.firebase:firebase-messaging:20.2.1' + implementation 'com.google.firebase:firebase-analytics:17.5.0' + implementation 'com.google.firebase:firebase-crashlytics:17.2.1' + + // Favorites parsing + implementation 'com.google.code.gson:gson:2.8.5' + api 'com.google.guava:guava:28.0-android' + + // Settings, About, Feedback implementation 'com.codemybrainsout.rating:ratingdialog:1.0.8' - implementation project(':about-box') - implementation ('com.mikepenz:aboutlibraries:6.1.1@aar') { transitive = true } - implementation ('org.rm3l:maoni:6.0.0@aar') { transitive = true } + implementation 'com.github.eggheadgames:android-about-box:2.0.1' + implementation 'com.mikepenz:aboutlibraries:8.3.1' + implementation 'org.rm3l:maoni:6.0.0' + implementation 'org.rm3l:maoni-common:6.0.0' implementation 'com.github.pschroen:slack-api-android:c66cc8d997' - implementation 'com.github.daniel-stoneuk:material-about-library:2.3.0' + + // Apollo + implementation 'com.apollographql.apollo:apollo-runtime:1.2.3' + implementation 'com.apollographql.apollo:apollo-http-cache:1.0.1' + implementation 'com.apollographql.apollo:apollo-rx2-support:1.0.1' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' + + // Google Play Billing + implementation 'com.android.billingclient:billing:3.0.0' + + // Testing dependencies + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:1.10.19' + testImplementation 'org.robolectric:robolectric:4.2.1' // Can't be upgraded until #5454 is fixed + testImplementation 'androidx.test:core:1.3.0' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test:rules:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestUtil 'androidx.test:orchestrator:1.3.0' } -apply plugin: 'com.google.gms.google-services' \ No newline at end of file +apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.apollographql.android' +apply plugin: 'com.google.firebase.crashlytics' diff --git a/app/src/androidTest/java/com/a494studios/koreanconjugator/ConjInfoActivityTest.java b/app/src/androidTest/java/com/a494studios/koreanconjugator/ConjInfoActivityTest.java new file mode 100644 index 00000000..24a6d18c --- /dev/null +++ b/app/src/androidTest/java/com/a494studios/koreanconjugator/ConjInfoActivityTest.java @@ -0,0 +1,94 @@ +package com.a494studios.koreanconjugator; + +import android.content.Intent; +import android.content.pm.ActivityInfo; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.a494studios.koreanconjugator.display.ConjInfoActivity; +import com.linearlistview.LinearListView; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class ConjInfoActivityTest { + private final String EXTRA_NAME = "Name"; + private final String EXTRA_CONJ = "conjugation"; + private final String EXTRA_PRON = "pronunciation"; + private final String EXTRA_ROME = "romanization"; + private ArrayList EXTRA_EXPL; + + @Rule + public ActivityTestRule activityRule = new ActivityTestRule(ConjInfoActivity.class) { + @Override + protected Intent getActivityIntent() { + EXTRA_EXPL = new ArrayList<>(); + EXTRA_EXPL.add("reason 1"); + EXTRA_EXPL.add("reason 2"); + EXTRA_EXPL.add("reason 3"); + + Intent intent = new Intent(); + intent.putExtra(ConjInfoActivity.EXTRA_NAME,EXTRA_NAME); + intent.putExtra(ConjInfoActivity.EXTRA_CONJ,EXTRA_CONJ); + intent.putExtra(ConjInfoActivity.EXTRA_PRON,EXTRA_PRON); + intent.putExtra(ConjInfoActivity.EXTRA_ROME,EXTRA_ROME); + intent.putExtra(ConjInfoActivity.EXTRA_EXPL,EXTRA_EXPL); + return intent; + } + }; + + @Test + public void test_displaysData() { + checkUI(); + } + + @Test + public void test_displayAfterRotation() { + try { + activityRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + Thread.sleep(100); + checkUI(); + activityRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + Thread.sleep(100); + checkUI(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + private void checkUI() { + onView(allOf(withId(R.id.displayCard_heading), isDescendantOfA(withId(R.id.info_infoCard)))) + .check(matches(isDisplayed())) + .check(matches(withText(EXTRA_NAME))); + onView(withId(R.id.conjInfo_conjugated)) + .check(matches(isDisplayed())) + .check(matches(withText(EXTRA_CONJ))); + onView(withId(R.id.conjInfo_hpronc)) + .check(matches(isDisplayed())) + .check(matches(withText(EXTRA_PRON))); + onView(withId(R.id.conjInfo_roman)) + .check(matches(isDisplayed())) + .check(matches(withText(EXTRA_ROME))); + + LinearListView listView = activityRule.getActivity().findViewById(R.id.conjInfo_explainList); + for(int i = 0;i activityRule = new ActivityTestRule(ConjugationActivity.class) { + @Override + protected Intent getActivityIntent() { + Intent intent = new Intent(); + intent.putExtra(ConjugationActivity.EXTRA_STEM,STEM); + intent.putExtra(ConjugationActivity.EXTRA_HONORIFIC,HONORIFIC); + intent.putExtra(ConjugationActivity.EXTRA_ISADJ,ISADJ); + return intent; + } + }; + + @Test + public void test_displaysData() { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + RecyclerView recyclerView = activityRule.getActivity().findViewById(R.id.conj_list); + int numItem = recyclerView.getAdapter().getItemCount(); + assertTrue(numItem > 0); + } + + @Test + public void test_displayAfterRotation() { + try { + Thread.sleep(100); + RecyclerView recyclerView = activityRule.getActivity().findViewById(R.id.conj_list); + int numItem = recyclerView.getAdapter().getItemCount(); + assertTrue(numItem > 0); + + activityRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + Thread.sleep(100); + assertEquals(numItem,recyclerView.getAdapter().getItemCount()); + + activityRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + Thread.sleep(100); + assertEquals(numItem,recyclerView.getAdapter().getItemCount()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Test + public void test_honorificSwitch() { + RecyclerView recyclerView = activityRule.getActivity().findViewById(R.id.conj_list); + int numItems = recyclerView.getAdapter().getItemCount(); + + try { + Thread.sleep(1000); + } catch (Exception e){ + e.printStackTrace(System.err); + } + + // Honorific + onView(withId(R.id.conj_switch)).perform(setChecked(true)); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + ConjugationCardsAdapter adapter = (ConjugationCardsAdapter)recyclerView.getAdapter(); + assertEquals(numItems,adapter.getItemCount()); + List conjugations = adapter.getItem(0); + assertTrue(conjugations.get(0).honorific); + + // Back to regular + onView(withId(R.id.conj_switch)).perform(setChecked(false)); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + adapter = (ConjugationCardsAdapter) recyclerView.getAdapter(); + assertEquals(numItems, adapter.getItemCount()); + conjugations = adapter.getItem(0); + assertFalse(conjugations.get(0).honorific); + } +} diff --git a/app/src/androidTest/java/com/a494studios/koreanconjugator/Utils.java b/app/src/androidTest/java/com/a494studios/koreanconjugator/Utils.java new file mode 100644 index 00000000..a802a7c2 --- /dev/null +++ b/app/src/androidTest/java/com/a494studios/koreanconjugator/Utils.java @@ -0,0 +1,48 @@ +package com.a494studios.koreanconjugator; + +import android.view.View; +import android.widget.Checkable; + +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +import static org.hamcrest.Matchers.isA; + +class Utils { + + // From: + // https://stackoverflow.com/questions/37819278/android-espresso-click-checkbox-if-not-checked/39650813#39650813 + public static ViewAction setChecked(final boolean checked) { + return new ViewAction() { + @Override + public BaseMatcher getConstraints() { + return new BaseMatcher() { + @Override + public boolean matches(Object item) { + return isA(Checkable.class).matches(item); + } + + @Override + public void describeMismatch(Object item, Description mismatchDescription) {} + + @Override + public void describeTo(Description description) {} + }; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public void perform(UiController uiController, View view) { + Checkable checkableView = (Checkable) view; + checkableView.setChecked(checked); + } + }; + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3cfcc6db..ec3432c8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,38 +1,57 @@ + + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> + + + + + + + + + + - - - - - + - + + android:value=".search.SearchActivity" /> - + + + android:value=".search.SearchActivity" /> + android:name=".search.SearchActivity" + android:noHistory="true" + android:theme="@style/AppTheme.NoTransition"> @@ -42,29 +61,48 @@ android:resource="@xml/searchable" /> - + - - + - + + - - + + + + + - - + + android:exported="false" + android:grantUriPermissions="true"> - + + + + + + \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/conjugation.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/conjugation.graphql new file mode 100644 index 00000000..0de9f144 --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/conjugation.graphql @@ -0,0 +1,13 @@ +query ConjugationQuery($stem: String!, $honorific: Boolean!, $isAdj: Boolean!, $regular: Boolean $conjugations:[String]) { + conjugations(stem: $stem, honorific: $honorific, isAdj: $isAdj, regular: $regular, conjugations: $conjugations) { + name, + conjugation, + type, + tense, + speechLevel, + honorific, + pronunciation, + romanization, + reasons + } +} \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/conjugation_names.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/conjugation_names.graphql new file mode 100644 index 00000000..3a053335 --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/conjugation_names.graphql @@ -0,0 +1,3 @@ +query ConjugationNamesQuery { + conjugationNames +} \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/entry.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/entry.graphql new file mode 100644 index 00000000..de815dff --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/entry.graphql @@ -0,0 +1,16 @@ +query EntryQuery($id: ID!) { + entry(id: $id){ + id, + term, + pos, + definitions, + antonyms, + synonyms, + examples { + sentence, + translation + } + regular, + note + } +} \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/examples.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/examples.graphql new file mode 100644 index 00000000..c233ff74 --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/examples.graphql @@ -0,0 +1,6 @@ +query ExamplesQuery($id: ID!) { + examples(id: $id){ + sentence, + translation + } +} \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/schema.json b/app/src/main/graphql/com/a494studios/koreanconjugator/schema.json new file mode 100644 index 00000000..cb676b31 --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/schema.json @@ -0,0 +1,1753 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": "", + "fields": [ + { + "name": "entries", + "description": "", + "args": [ + { + "name": "term", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Entry", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "entry", + "description": "", + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Entry", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "examples", + "description": "", + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Example", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "conjugations", + "description": "", + "args": [ + { + "name": "stem", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "isAdj", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "honorific", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "regular", + "description": "", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "conjugations", + "description": "", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Conjugation", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "conjugationTypes", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "conjugationNames", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "search", + "description": "", + "args": [ + { + "name": "query", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "cursor", + "description": "", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Result", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "wordOfTheDay", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Entry", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "stems", + "description": "", + "args": [ + { + "name": "term", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Entry", + "description": "", + "fields": [ + { + "name": "id", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "term", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pos", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "definitions", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "antonyms", + "description": "", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "synonyms", + "description": "", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "examples", + "description": "", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Example", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "regular", + "description": "", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "note", + "description": "", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Example", + "description": "", + "fields": [ + { + "name": "sentence", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "translation", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Conjugation", + "description": "", + "fields": [ + { + "name": "name", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "conjugation", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tense", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Tense", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "speechLevel", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SpeechLevel", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "honorific", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pronunciation", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "romanization", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reasons", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "Tense", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "PRESENT", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PAST", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FUTURE", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NONE", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "SpeechLevel", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "FORMAL_LOW", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INFORMAL_LOW", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INFORMAL_HIGH", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FORMAL_HIGH", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NONE", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Result", + "description": "", + "fields": [ + { + "name": "cursor", + "description": "", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "results", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Entry", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": "Location adjacent to a variable definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "CacheControlScope", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "PUBLIC", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PRIVATE", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Upload", + "description": "The `Upload` scalar type represents a file upload.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } + } +} diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/search.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/search.graphql new file mode 100644 index 00000000..864ec054 --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/search.graphql @@ -0,0 +1,11 @@ +query SearchQuery($query: String!, $cursor: Int) { + search(query: $query, cursor: $cursor) { + results { + id, + term, + pos, + definitions + }, + cursor + } +} \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/stems.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/stems.graphql new file mode 100644 index 00000000..994527da --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/stems.graphql @@ -0,0 +1,3 @@ +query StemQuery($term: String!) { + stems(term: $term) +} \ No newline at end of file diff --git a/app/src/main/graphql/com/a494studios/koreanconjugator/wod.graphql b/app/src/main/graphql/com/a494studios/koreanconjugator/wod.graphql new file mode 100644 index 00000000..06cac094 --- /dev/null +++ b/app/src/main/graphql/com/a494studios/koreanconjugator/wod.graphql @@ -0,0 +1,6 @@ +query WordOfTheDayQuery { + wordOfTheDay { + id, + term + } +} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/ConjugationAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/ConjugationAdapter.java deleted file mode 100644 index 09f58e3d..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/ConjugationAdapter.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.a494studios.koreanconjugator; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.a494studios.koreanconjugator.parsing.Formality; - -import java.util.ArrayList; - -public class ConjugationAdapter extends BaseAdapter { - - private ArrayList conjugations; - private static final int RESOURCE_ID = R.layout.item_conjugation; - - public ConjugationAdapter(ArrayList conjugations) { - this.conjugations = conjugations; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); - } - Conjugation c = conjugations.get(i); - TextView typeView = view.findViewById(R.id.conjFormal); - TextView conjView = view.findViewById(R.id.conjText); - if(c.getFormality() == Formality.NONE){ - typeView.setText(c.getForm().toString()); - }else { - typeView.setText(c.getFormality().toString()); - } - conjView.setText(c.getConjugated()); - return view; - } - - @Override - public int getCount() { - return conjugations.size(); - } - - @Override - public Object getItem(int i) { - return conjugations.get(i); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/ConjugationCardFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/ConjugationCardFragment.java deleted file mode 100644 index 18b83aae..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/ConjugationCardFragment.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.a494studios.koreanconjugator; - - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.linearlistview.LinearListView; - -import java.util.ArrayList; - - -/** - * A simple {@link Fragment} subclass. - * Use the {@link ConjugationCardFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class ConjugationCardFragment extends Fragment { - private static final String ARG_HEADING = "HEADINGS"; - private static final String ARG_CONJUGATIONS = "CONJUGATIONS"; - - private String heading; - private ArrayList conjugations; - - - public ConjugationCardFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param heading Parameter 1. - * @param conjugations Parameter 2. - * @return A new instance of fragment ConjugationCardFragment. - */ - public static ConjugationCardFragment newInstance(String heading, ArrayList conjugations) { - ConjugationCardFragment fragment = new ConjugationCardFragment(); - Bundle args = new Bundle(); - args.putString(ARG_HEADING, heading); - args.putSerializable(ARG_CONJUGATIONS, conjugations); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - heading = getArguments().getString(ARG_HEADING); - conjugations = (ArrayList)getArguments().getSerializable(ARG_CONJUGATIONS); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_conjugation_card, container, false); - TextView textView = view.findViewById(R.id.conjCard_heading); - LinearListView listView = view.findViewById(R.id.conjCard_list); - ConjugationAdapter adapter = new ConjugationAdapter(conjugations); - - textView.setText(heading); - listView.setAdapter(adapter); - - return view; - } - -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/CustomApplication.java b/app/src/main/java/com/a494studios/koreanconjugator/CustomApplication.java new file mode 100644 index 00000000..e266e0af --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/CustomApplication.java @@ -0,0 +1,186 @@ +package com.a494studios.koreanconjugator; + +import android.content.Context; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.multidex.MultiDex; +import androidx.multidex.MultiDexApplication; + +import com.a494studios.koreanconjugator.display.DisplayCardView; +import com.a494studios.koreanconjugator.display.cards.AdCard; +import com.a494studios.koreanconjugator.utils.Logger; +import com.a494studios.koreanconjugator.utils.Utils; +import com.android.billingclient.api.AcknowledgePurchaseParams; +import com.android.billingclient.api.AcknowledgePurchaseResponseListener; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.apollographql.apollo.ApolloClient; +import com.apollographql.apollo.cache.http.ApolloHttpCache; +import com.apollographql.apollo.cache.http.DiskLruHttpCacheStore; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdView; +import com.google.android.gms.ads.MobileAds; +import com.google.firebase.analytics.FirebaseAnalytics; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + +import okhttp3.OkHttpClient; + +public class CustomApplication extends MultiDexApplication implements PurchasesUpdatedListener { + private static final String APP_ID = BuildConfig.ADMOB_KEY; + private static final String SERVER_URL = com.a494studios.koreanconjugator.BuildConfig.SERVER_URL; + + private static ApolloClient apolloClient; + private static boolean isAdFree = false; + private static boolean billingConnected = false; + private static BillingClient billingClient; + + // Called when the application is starting, before any other application objects have been created. + @Override + public void onCreate() { + super.onCreate(); + + // Setup ads + MobileAds.initialize(this, APP_ID); + + // Check preferences first, to save us a billing request + Boolean prefAdFree = Utils.isAdFree(getApplicationContext()); + if(prefAdFree != null) { + System.out.println("Got Ad Free from prefs"); + isAdFree = prefAdFree; + } + + // Setup response cache for Apollo + File file = this.getCacheDir(); + int size = 1024*1024; + DiskLruHttpCacheStore cacheStore = new DiskLruHttpCacheStore(file,size); + + //Build the Apollo Client + OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); + apolloClient = ApolloClient.builder() + .serverUrl(SERVER_URL) + .okHttpClient(okHttpClient) + .httpCache(new ApolloHttpCache(cacheStore)) + .build(); + + // Setup Google Play Billing + billingClient = BillingClient.newBuilder(this) + .setListener(this) + .enablePendingPurchases() + .build(); + billingClient.startConnection(new BillingClientStateListener() { + @Override + public void onBillingSetupFinished(@NotNull BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + System.out.println("Connected to Google Play Billing"); + billingConnected = true; + + if(prefAdFree == null) { + // Nothing saved in preferences, check purchase history if an upgrade was bought + billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, (result, list) -> { + if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { + System.out.println("Got Ad Free from purchases"); + isAdFree = list.size() > 0 && list.get(0).getSku().equals(Utils.SKU_AD_FREE); + Utils.setAdFree(getApplicationContext(), isAdFree); + } else { + System.out.println("Error occurred when fetching purchase history:" + result.getDebugMessage()); + } + }); + } + } else { + System.out.println("Error connecting: " + billingResult.getResponseCode()); + billingConnected = false; + } + } + + @Override + public void onBillingServiceDisconnected() { + System.out.println("Disconnected from Google Play Billing"); + billingConnected = false; + } + }); + + // Setup Firebase Analytics + Logger.initialize(FirebaseAnalytics.getInstance(this)); + } + + @Override + protected void attachBaseContext(Context context) { + super.attachBaseContext(context); + MultiDex.install(this); + } + + public static ApolloClient getApolloClient() { + return apolloClient; + } + + public static void handleAdCard(DisplayCardView cardView, String adId){ + if(isAdFree) { + cardView.setVisibility(View.GONE); + } else { + cardView.setCardBody(new AdCard(adId)); + } + } + + public static void handleAdCard(AdView adView) { + if(isAdFree) { + adView.setVisibility(View.GONE); + } else { + adView.loadAd(new AdRequest.Builder().build()); + } + } + + @Override + public void onPurchasesUpdated(@NotNull BillingResult billingResult, @Nullable List list) { + if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK + && !list.isEmpty() && list.get(0).getSku().equals(Utils.SKU_AD_FREE)) { + + if(list.get(0).getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + AcknowledgePurchaseParams consumeParams = AcknowledgePurchaseParams + .newBuilder() + .setPurchaseToken(list.get(0).getPurchaseToken()) + .build(); + + // Weird bug requires this not be a lambda + AcknowledgePurchaseResponseListener listener = new AcknowledgePurchaseResponseListener() { + @Override + public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) { + String toastMsg = ""; + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + System.out.println("Purchase Acknowledged"); + isAdFree = true; + Utils.setAdFree(CustomApplication.this, true); + toastMsg = "Upgrade Success! Please restart Hanji for the upgrade to take affect"; + } else { + toastMsg = "An error occurred with your purchase, please contact support"; + } + Toast.makeText(CustomApplication.this, toastMsg, Toast.LENGTH_LONG).show(); + } + }; + + billingClient.acknowledgePurchase(consumeParams, listener); + } else if (list.get(0).getPurchaseState() == Purchase.PurchaseState.PENDING) { + Toast.makeText(this, "Payment pending. Hanji will be upgraded once payment is received", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(this, "An error occurred with your purchase, please contact support", Toast.LENGTH_LONG).show(); + } + } + } + + public static boolean isBillingConnected() { + return billingConnected; + } + + public static BillingClient getBillingClient() { + return billingClient; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/DisplayActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/DisplayActivity.java deleted file mode 100644 index 4247b534..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/DisplayActivity.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.a494studios.koreanconjugator; - -import android.app.SearchManager; -import android.content.DialogInterface; -import android.content.Intent; -import android.support.design.widget.Snackbar; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; -import android.support.v7.widget.SearchView; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.a494studios.koreanconjugator.parsing.Category; -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.a494studios.koreanconjugator.parsing.Form; -import com.a494studios.koreanconjugator.parsing.Formality; -import com.a494studios.koreanconjugator.parsing.Server; -import com.a494studios.koreanconjugator.parsing.Tense; -import com.a494studios.koreanconjugator.settings.SettingsActivity; -import com.a494studios.koreanconjugator.utils.ErrorDialogFragment; -import com.android.volley.NoConnectionError; -import com.crashlytics.android.Crashlytics; -import com.eggheadgames.aboutbox.activity.AboutActivity; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdView; -import com.transitionseverywhere.Fade; -import com.transitionseverywhere.Transition; -import com.transitionseverywhere.TransitionManager; - -import org.rm3l.maoni.Maoni; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Map.Entry; - -public class DisplayActivity extends AppCompatActivity { - - public static final String EXTRA_CONJ = "conj"; - public static final String EXTRA_DEF = "definition"; - - private String definition; - private String infinitive; - private TextView defView; - private boolean overflowClicked; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_display); - defView = findViewById(R.id.defCard_content); - AdView adView = findViewById(R.id.display_adView); - ArrayList conjugations = (ArrayList)getIntent().getSerializableExtra(EXTRA_CONJ); - if(savedInstanceState != null){ - definition = savedInstanceState.getString(EXTRA_DEF); - }else{ - definition = getIntent().getStringExtra(EXTRA_DEF); - } - - if(conjugations == null){ // Null and empty check for extra - ErrorDialogFragment.newInstance().setListener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - onBackPressed(); - } - }).show(getSupportFragmentManager(),"error_dialog"); - Crashlytics.log("Conjugations was null in DisplayActivity"); - return; - }else if(conjugations.isEmpty()){ - ErrorDialogFragment.newInstance().setListener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - onBackPressed(); - } - }).show(getSupportFragmentManager(),"error_dialog"); - Crashlytics.log("Conjugations was empty in DisplayActivity"); - return; - } - - infinitive = conjugations.get(0).getInfinitive(); - adView.loadAd(new AdRequest.Builder().build()); - - ActionBar actionBar = getSupportActionBar(); - if(actionBar != null) { - actionBar.setTitle("Result: "+infinitive); - } - - if(definition == null) { - requestDefinition(); - }else{ - defView.setText(definition); - } - - // Declarative - ArrayList decPast = Category.Categories.getSubSet(conjugations,null, Form.DECLARATIVE, Tense.PAST); - ArrayList decPres = Category.Categories.getSubSet(conjugations,null, Form.DECLARATIVE, Tense.PRESENT); - ArrayList decFut = Category.Categories.getSubSet(conjugations,null, Form.DECLARATIVE, Tense.FUTURE); - ArrayList decFutC = Category.Categories.getSubSet(conjugations,null, Form.DECLARATIVE, Tense.FUT_COND); - // Inquisitive - ArrayList inqPast = Category.Categories.getSubSet(conjugations, null, Form.INQUISITIVE, Tense.PAST); - ArrayList inqPres = Category.Categories.getSubSet(conjugations,null, Form.INQUISITIVE, Tense.PRESENT); - // Imperative - ArrayList imPres = Category.Categories.getSubSet(conjugations,null, Form.IMPERATIVE, Tense.PRESENT); - // Propositive - ArrayList propPres = Category.Categories.getSubSet(conjugations,null, Form.PROPOSITIVE, Tense.PRESENT); - // Other - ArrayList other = Category.Categories.getSubSet(conjugations,Form.NOMINAL,Form.CON_AND,Form.CON_IF,Form.CON_BUT,Form.ADJ); - // Favorites - ArrayList> map = Utils.getFavorites(this); - ArrayList> conjMap = new ArrayList<>(); - for(Entry entry: map){ - Category[] categories = entry.getValue(); - if(categories != null && categories.length == 3) { - Conjugation c = Category.Categories.getSubSet(conjugations, (Formality) categories[0], (Form) categories[1], (Tense) categories[2]).get(0); - conjMap.add(new AbstractMap.SimpleEntry<>(entry.getKey(), c)); - } - } - - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - if(!conjMap.isEmpty()) { - transaction.replace(R.id.frag_1, FavoritesFragment.newInstance(conjMap)); - }else{ - findViewById(R.id.frag_1).setVisibility(View.GONE); - } - transaction.replace(R.id.frag_2,ConjugationCardFragment.newInstance("Declarative Past", decPast)); - transaction.replace(R.id.frag_3,ConjugationCardFragment.newInstance("Declarative Present", decPres)); - transaction.replace(R.id.frag_4,ConjugationCardFragment.newInstance("Declarative Future", decFut)); - transaction.replace(R.id.frag_5,ConjugationCardFragment.newInstance("Declarative Future Conditional", decFutC)); - transaction.replace(R.id.frag_6,ConjugationCardFragment.newInstance("Inquisitive Past", inqPast)); - transaction.replace(R.id.frag_7,ConjugationCardFragment.newInstance("Inquisitive Present", inqPres)); - transaction.replace(R.id.frag_8,ConjugationCardFragment.newInstance("Imperative Present", imPres)); - transaction.replace(R.id.frag_9,ConjugationCardFragment.newInstance("Propositive Present", propPres)); - transaction.replace(R.id.frag_10,ConjugationCardFragment.newInstance("Other Forms", other)); - transaction.commit(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.search_menu, menu); - SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); - SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); - searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == R.id.overflow_settings){ - overflowClicked = true; - startActivity(new Intent(getBaseContext(), SettingsActivity.class)); - return true; - }else if(item.getItemId() == R.id.overflow_about){ - overflowClicked = true; - Utils.makeAboutBox(this); - AboutActivity.launch(this); - return true; - }else if(item.getItemId() == R.id.overflow_bug) { - Maoni maoni = Utils.makeMaoniActivity(DisplayActivity.this); - if(maoni != null){ - maoni.start(DisplayActivity.this); - } - return true; - } else{ - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putString(EXTRA_DEF, definition); - super.onSaveInstanceState(savedInstanceState); - } - - @Override - public void onWindowFocusChanged (boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if(hasFocus){ - ArrayList fragViews = makeFragViewList(); - for (int i = 0; i < fragViews.size(); i++) { - ViewGroup v = fragViews.get(i); - Transition t = new Fade(Fade.IN); - t.setStartDelay(((i + 1) * 80)); - TransitionManager.beginDelayedTransition(v, t); - v.setVisibility(View.VISIBLE); - } - } - } - - @Override - public void onPause(){ - super.onPause(); - if(!overflowClicked) overridePendingTransition(0,0); - } - - @Override - public void onResume(){ - super.onResume(); - overflowClicked = false; - } - - private void handleError(Exception error) { - Snackbar snackbar; - if (error instanceof NoConnectionError) { - snackbar = Snackbar.make(findViewById(R.id.disp_root), "Lost connection", Snackbar.LENGTH_INDEFINITE); - }else{ - snackbar = Snackbar.make(findViewById(R.id.disp_root), "Couldn't connect to server", Snackbar.LENGTH_INDEFINITE); - System.err.println(error.toString()); - } - snackbar.setAction("Retry", new View.OnClickListener() { - @Override - public void onClick(View view) { - requestDefinition(); - } - }); - snackbar.show(); - } - - private void requestDefinition(){ - Server.requestKorDefinition(infinitive, this, new Server.DefinitionListener() { - @Override - public void onDefinitionReceived(String result) { - definition = result; - defView.setText(definition); - } - - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - } - - private ArrayList makeFragViewList(){ - ArrayList views = new ArrayList<>(); - views.add((ViewGroup) findViewById(R.id.disp_defCard)); - views.add((ViewGroup) findViewById(R.id.frag_1)); - views.add((ViewGroup) findViewById(R.id.frag_2)); - views.add((ViewGroup) findViewById(R.id.frag_3)); - views.add((ViewGroup) findViewById(R.id.frag_4)); - views.add((ViewGroup) findViewById(R.id.frag_5)); - views.add((ViewGroup) findViewById(R.id.frag_6)); - views.add((ViewGroup) findViewById(R.id.frag_7)); - views.add((ViewGroup) findViewById(R.id.frag_8)); - views.add((ViewGroup) findViewById(R.id.frag_9)); - views.add((ViewGroup) findViewById(R.id.frag_10)); - return views; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/FavoritesFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/FavoritesFragment.java deleted file mode 100644 index de01cd7c..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/FavoritesFragment.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.a494studios.koreanconjugator; - - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.linearlistview.LinearListView; - -import java.util.ArrayList; -import java.util.Map.Entry; - - -/** - * A simple {@link Fragment} subclass. - * Use the {@link FavoritesFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class FavoritesFragment extends Fragment { - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - private static final String ARG_ENTRIES = "PAST_CONJUGATION"; - - private ArrayList> entries; - public FavoritesFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param entries Parameter 1. - * @return A new instance of fragment FavoritesFragment. - */ - public static FavoritesFragment newInstance(ArrayList> entries) { - FavoritesFragment fragment = new FavoritesFragment(); - Bundle args = new Bundle(); - args.putSerializable(ARG_ENTRIES, entries); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - entries = (ArrayList>)getArguments().getSerializable(ARG_ENTRIES); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_favorites, container, false); - - LinearListView listView = view.findViewById(R.id.favCard_list); - listView.setAdapter(new FavoritesAdapter(entries)); - return view; - } - - - private class FavoritesAdapter extends BaseAdapter { - - private ArrayList> entries; - private static final int RESOURCE_ID = R.layout.item_conjugation; - - public FavoritesAdapter(ArrayList> entries) { - this.entries = entries; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); - } - Entry entry = entries.get(i); - TextView typeView = view.findViewById(R.id.conjFormal); - TextView conjView = view.findViewById(R.id.conjText); - typeView.setText(entry.getKey()); - conjView.setText(entry.getValue().getConjugated()); - return view; - } - - @Override - public int getCount() { - return entries.size(); - } - - @Override - public Object getItem(int i) { - return entries.get(i); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - } - - -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/LaunchActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/LaunchActivity.java new file mode 100644 index 00000000..4e78cc97 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/LaunchActivity.java @@ -0,0 +1,100 @@ +package com.a494studios.koreanconjugator; + +import android.content.Intent; +import android.os.Bundle; + +import com.a494studios.koreanconjugator.parsing.Favorite; +import com.a494studios.koreanconjugator.utils.Utils; +import com.codemybrainsout.onboarder.AhoyOnboarderActivity; +import com.codemybrainsout.onboarder.AhoyOnboarderCard; + +import java.util.ArrayList; + +public class LaunchActivity extends AhoyOnboarderActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if(!Utils.isFirstBoot(this) && !Utils.isFirstTwo(this)){ + Intent intent = new Intent(this,MainActivity.class); + startActivity(intent); + finish(); + return; + } + + // Make default favorites, or clear old favorites from 1.0 + ArrayList favs = new ArrayList<>(); + favs.add(new Favorite("Past","declarative past informal high",false)); + favs.add(new Favorite("Present","declarative present informal high",false)); + favs.add(new Favorite("Future","declarative future informal high",false)); + Utils.setFavorites(favs,this); + Utils.setFirstBoot(this,false); + Utils.setFirstTwo(this, false); + + + // First card + String title = getString(R.string.onboarding1_title); + String desc = getString(R.string.onboarding1_desc); + int size = (int)dpToPixels(200, this); + int marginVertical = (int)dpToPixels(72, this); + int marginHorizontal = (int)dpToPixels(16, this); + + AhoyOnboarderCard card1 = new AhoyOnboarderCard(title, desc, R.drawable.ic_icon); + card1.setBackgroundColor(R.color.white); + card1.setTitleColor(R.color.black); + card1.setDescriptionColor(R.color.black); + card1.setTitleTextSize(32); + card1.setDescriptionTextSize(16); + card1.setIconLayoutParams(size, size, marginVertical, marginHorizontal, marginHorizontal, 0); + + // Second card + title = getString(R.string.onboarding2_title); + desc = getString(R.string.onboarding2_desc); + size = (int)dpToPixels(350, this); + marginVertical = (int)dpToPixels(24, this); + + AhoyOnboarderCard card2 = new AhoyOnboarderCard(title, desc, R.drawable.ic_word_cloud); + card2.setBackgroundColor(R.color.white); + card2.setTitleColor(R.color.black); + card2.setDescriptionColor(R.color.black); + card2.setTitleTextSize(32); + card2.setDescriptionTextSize(16); + card2.setIconLayoutParams(size, size, marginVertical, marginHorizontal, marginHorizontal, 0); + + // Third card + title = getString(R.string.onboarding3_title); + desc = getString(R.string.onboarding3_desc); + int width = (int)dpToPixels(200, this); + int height = (int)dpToPixels(232, this); + marginVertical = (int)dpToPixels(72, this); + + AhoyOnboarderCard card3 = new AhoyOnboarderCard(title, desc, R.drawable.ic_form); + card3.setBackgroundColor(R.color.white); + card3.setTitleColor(R.color.black); + card3.setDescriptionColor(R.color.black); + card3.setTitleTextSize(32); + card3.setDescriptionTextSize(16); + card3.setIconLayoutParams(width, height, marginVertical, marginHorizontal, marginHorizontal, 0); + + ArrayList pages = new ArrayList<>(); + pages.add(card1); + pages.add(card2); + pages.add(card3); + + ArrayList colors = new ArrayList<>(); + colors.add(R.color.colorPrimary); + colors.add(R.color.colorAccent); + colors.add(R.color.orangeYellow); + + setColorBackground(colors); + setOnboardPages(pages); + setFinishButtonTitle(R.string.finish_btn_text); + } + + @Override + public void onFinishButtonPressed() { + Intent intent = new Intent(this,MainActivity.class); + startActivity(intent); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/MainActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/MainActivity.java index b38a2619..2b3ab329 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/MainActivity.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/MainActivity.java @@ -1,103 +1,76 @@ package com.a494studios.koreanconjugator; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.PorterDuff; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import android.os.Bundle; -import android.support.v7.widget.CardView; -import android.support.v7.widget.PopupMenu; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.ProgressBar; +import android.view.Menu; +import android.view.MenuInflater; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import com.a494studios.koreanconjugator.parsing.Category; -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.a494studios.koreanconjugator.parsing.Form; -import com.a494studios.koreanconjugator.parsing.Formality; -import com.a494studios.koreanconjugator.parsing.Server; -import com.a494studios.koreanconjugator.parsing.Tense; -import com.a494studios.koreanconjugator.settings.SettingsActivity; +import com.a494studios.koreanconjugator.display.DisplayCardView; +import com.a494studios.koreanconjugator.utils.BaseActivity; +import com.a494studios.koreanconjugator.utils.ScrollViewAnimationHandler; import com.a494studios.koreanconjugator.utils.SlackHandler; +import com.a494studios.koreanconjugator.utils.Utils; import com.codemybrainsout.ratingdialog.RatingDialog; -import com.crashlytics.android.Crashlytics; +import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.eggheadgames.aboutbox.AboutBoxUtils; import com.eggheadgames.aboutbox.AboutConfig; -import com.eggheadgames.aboutbox.activity.AboutActivity; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdView; -import com.google.android.gms.ads.MobileAds; -import org.rm3l.maoni.Maoni; +public class MainActivity extends BaseActivity { -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import allbegray.slack.SlackClientFactory; - -public class MainActivity extends AppCompatActivity { - - private static final String APP_ID = BuildConfig.ADMOB_KEY; - - private ProgressBar progressBar; - private TextView loadingText; - private CardView searchCard; - private EditText editText; + private SearchCard searchCard; private TextView logo; - private ImageView overflowMenu; - private boolean searchInProgress; + private ScrollViewAnimationHandler animationHandler; + private LinearLayout linearLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - MobileAds.initialize(this, APP_ID); - progressBar = findViewById(R.id.main_loadingBar); - loadingText = findViewById(R.id.main_loadingText); - overflowMenu = findViewById(R.id.main_menu_icon); - searchCard = findViewById(R.id.main_searchCard); - editText = findViewById(R.id.main_editText); - logo = findViewById(R.id.main_logo); - AdView adView = findViewById(R.id.main_adView); - searchInProgress = false; + logo = findViewById(R.id.main_extendedBar); - adView.loadAd(new AdRequest.Builder().build()); + // Set up Ad, Search, and Word of the Day cards + DisplayCardView wodCard = findViewById(R.id.main_wodCard); + DisplayCardView searchCard = findViewById(R.id.main_searchCard); + this.searchCard = new SearchCard(this); + + CustomApplication.handleAdCard(findViewById(R.id.main_adView), getString(R.string.MAIN_AD_ID)); + wodCard.setCardBody(new WordOfDayCard()); + searchCard.setCardBody(this.searchCard); + + ActionBar actionBar = getSupportActionBar(); + if(actionBar != null) { + actionBar.setTitle(""); + actionBar.setElevation(0); + } + FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); try { if (getIntent().getExtras() != null) { - Crashlytics.log("Extra in Main"); + crashlytics.log("Extra in Main"); for (String s : getIntent().getExtras().keySet()) { - Crashlytics.log("Key: " + s); + FirebaseCrashlytics.getInstance().log("Key: " + s); if (getIntent().getStringExtra(s) != null) { - Crashlytics.setString(s, getIntent().getStringExtra(s)); + crashlytics.setCustomKey(s, getIntent().getStringExtra(s)); } else if (getIntent().getSerializableExtra(s) != null) { - Crashlytics.setString(s, getIntent().getSerializableExtra(s).toString()); + crashlytics.setCustomKey(s, getIntent().getSerializableExtra(s).toString()); } else { - Crashlytics.setString(s, "null"); + crashlytics.setCustomKey(s, "null"); } } String showDialog = getIntent().getStringExtra("dialog"); if (showDialog != null && showDialog.equals("true")) { - Crashlytics.log("Dialog is not null and true"); + FirebaseCrashlytics.getInstance().log("Dialog is not null and true"); AlertDialog.Builder builder = new AlertDialog.Builder(this); String title = getIntent().getStringExtra("title"); String msg = getIntent().getStringExtra("message"); if (title != null && msg != null) { - Crashlytics.log("Title and msg not null, building dialog"); + FirebaseCrashlytics.getInstance().log("Title and msg not null, building dialog"); builder.setTitle(title); builder.setMessage(msg); builder.create().show(); @@ -105,125 +78,10 @@ protected void onCreate(Bundle savedInstanceState) { } } } catch (Exception e) { - Crashlytics.log("Exception caught by try-catch block"); - Crashlytics.logException(e); + crashlytics.log("Exception caught by try-catch block"); + crashlytics.recordException(e); } - if(Utils.isFirstBoot(this)){ - // Make default favorites - Category[] past = {Formality.INFORMAL_HIGH, Form.DECLARATIVE, Tense.PAST}; - Category[] present = {Formality.INFORMAL_HIGH, Form.DECLARATIVE, Tense.PRESENT}; - Category[] future = {Formality.INFORMAL_HIGH, Form.DECLARATIVE, Tense.FUTURE}; - ArrayList> favorites = new ArrayList<>(); - favorites.add(new AbstractMap.SimpleEntry<>("Past",past)); - favorites.add(new AbstractMap.SimpleEntry<>("Present",present)); - favorites.add(new AbstractMap.SimpleEntry<>("Future",future)); - Utils.setFavorites(favorites,this); - Utils.setFirstBoot(this,false); - } - - progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN); - progressBar.getProgressDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN); - - // Handle Search - editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - View view = MainActivity.this.getCurrentFocus(); - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (view != null && imm != null) { - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - progressBar.setVisibility(View.VISIBLE); - loadingText.setVisibility(View.VISIBLE); - searchCard.setVisibility(View.INVISIBLE); - overflowMenu.setVisibility(View.INVISIBLE); - logo.setVisibility(View.INVISIBLE); - searchInProgress = true; - - final String entry = editText.getText().toString().trim(); - if(Utils.isHangul(entry)) { - Crashlytics.log("Korean search: "+entry); - Crashlytics.setString("searchTerm",entry); - doKoreanSearch(entry); - }else if(entry.matches("[A-Za-z ]+")){ // Check if String in English - Crashlytics.log("English search: "+entry); - Crashlytics.setString("searchTerm",entry); - Server.requestEngDefinition(entry, getApplicationContext(), new Server.ServerListener() { - @Override - public void onResultReceived(ArrayList conjugations, HashMap searchResults) { - if(searchResults != null) { - if(searchResults.isEmpty()) { - showSearchCard(); - AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.no_results_title) - .setMessage(R.string.no_results_msg) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }) - .create(); - if(!MainActivity.this.isFinishing()) { - dialog.show(); - } - }else if (searchResults.size() == 1 || Utils.getEnglishLuck(getBaseContext())) { - doKoreanSearch(searchResults.keySet().iterator().next()); // Get the first key in map - }else{ - goToSearchResults(searchResults,entry); - } - } - } - @Override - public void onErrorOccurred(Exception error) { - Utils.handleError(error,MainActivity.this); - showSearchCard(); - } - }); - }else{ - Crashlytics.log("Invalid Search: "+entry); - Crashlytics.setString("searchTerm",entry); - showSearchCard(); - Toast.makeText(getBaseContext(),"Input not Valid",Toast.LENGTH_LONG).show(); - } - } - return false; - } - }); - - // Setting up Overflow Menu - overflowMenu.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - PopupMenu popup = new PopupMenu(MainActivity.this, view, Gravity.END); - popup.inflate(R.menu.main_menu); - popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - if(item.getItemId() == R.id.overflow_settings){ - startActivity(new Intent(getBaseContext(), SettingsActivity.class)); - return true; - }else if(item.getItemId() == R.id.overflow_about){ - Utils.makeAboutBox(MainActivity.this); - AboutActivity.launch(MainActivity.this); - return true; - }else if(item.getItemId() == R.id.overflow_bug){ - Maoni maoni = Utils.makeMaoniActivity(MainActivity.this); - if(maoni != null){ - maoni.start(MainActivity.this); - } - return true; - } - return false; - } - }); - popup.show(); - } - }); - // Setting up Feedback dialog final RatingDialog ratingDialog = new RatingDialog.Builder(this) .threshold(5) // Get feedback if less than 5 stars @@ -231,143 +89,55 @@ public boolean onMenuItemClick(MenuItem item) { .title(getString(R.string.feed_title)) .formTitle(getString(R.string.feed_form_title)) .formHint(getString(R.string.feed_form_hint)) - .onThresholdCleared(new RatingDialog.Builder.RatingThresholdClearedListener() { - @Override - public void onThresholdCleared(final RatingDialog ratingDialog, final float rating, boolean thresholdCleared) { - ratingDialog.dismiss(); - final AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.feed_rate_title) - .setMessage(R.string.feed_rate_msg) - .setPositiveButton(R.string.feed_rate_sure, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - //AboutConfig config = AboutConfig.getInstance(); - //AboutBoxUtils.openApp(MainActivity.this, config.buildType,config.packageName); - AboutBoxUtils.openApp(MainActivity.this, AboutConfig.BuildType.GOOGLE,"com.a494studios.koreanconjugator"); - } - }) - .setNegativeButton(R.string.feed_rate_never, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // Do nothing - } - }).create(); - dialog.setOnShowListener( new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface arg0) { - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.grey_500)); - } - }); - dialog.show(); - } + .onThresholdCleared((ratingDialog1, rating, thresholdCleared) -> { + ratingDialog1.dismiss(); + final AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) + .setTitle(R.string.feed_rate_title) + .setMessage(R.string.feed_rate_msg) + .setPositiveButton(R.string.feed_rate_sure, (dialogInterface, i) -> + AboutBoxUtils.openApp(MainActivity.this, AboutConfig.BuildType.GOOGLE,"com.a494studios.koreanconjugator")) + .setNegativeButton(R.string.feed_rate_never, (dialogInterface, i) -> {/*Do Nothing*/}) + .create(); + dialog.setOnShowListener(arg0 -> + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.grey_500))); + dialog.show(); }) - .onRatingBarFormSumbit(new RatingDialog.Builder.RatingDialogFormListener() { - @Override - public void onFormSubmitted(String feedback) { - SlackHandler handler = new SlackHandler(MainActivity.this); - if(handler.auth()) { - handler.sendFeedback(feedback); - Toast.makeText(MainActivity.this,"Thanks for the feedback!",Toast.LENGTH_LONG).show(); - }else{ - Toast.makeText(MainActivity.this,"Couldn't connect to server",Toast.LENGTH_LONG).show(); - } - - } - }).build(); - ratingDialog.show(); - } - - @Override - public void onResume(){ - super.onResume(); - if(!searchInProgress) { - showSearchCard(); - } - } - - private void doKoreanSearch(final String entry){ - Server.requestKoreanSearch(entry, getApplicationContext(), new Server.ServerListener() { - @Override - public void onResultReceived(final ArrayList conjugations, HashMap searchResults) { - if (conjugations != null) { - goToDisplay(conjugations); - } else if (searchResults != null && !searchResults.isEmpty()) { - if(Utils.getKoreanLuck(getApplicationContext())){ - requestConjugations(searchResults.keySet().iterator().next()); + .onRatingBarFormSumbit(feedback -> { + SlackHandler handler = new SlackHandler(MainActivity.this); + if(handler.auth()) { + handler.sendFeedback(feedback); + Toast.makeText(MainActivity.this,"Thanks for the feedback!",Toast.LENGTH_LONG).show(); }else{ - goToSearchResults(searchResults,entry); - } - } else{ - AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.no_results_title) - .setMessage(R.string.no_results_msg) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }) - .create(); - if(!MainActivity.this.isFinishing()) { - dialog.show(); + Toast.makeText(MainActivity.this,"Couldn't connect to server",Toast.LENGTH_LONG).show(); } - } - } - @Override - public void onErrorOccurred(Exception error) { - Utils.handleError(error,MainActivity.this); - showSearchCard(); - } - }); - } + }) + .build(); + ratingDialog.show(); - private void requestConjugations(String word){ - Server.requestConjugation(word, this, new Server.ServerListener() { - @Override - public void onResultReceived(ArrayList conjugations, HashMap searchResults) { - goToDisplay(conjugations); - } - @Override - public void onErrorOccurred(Exception error) { - Utils.handleError(error,MainActivity.this); - showSearchCard(); - } - }); + // Setup animations + linearLayout = findViewById(R.id.main_linearLayout); + animationHandler = new ScrollViewAnimationHandler(this, logo, findViewById(R.id.main_scrollView)); + animationHandler.setupScrollAnimation(linearLayout); } - private void showSearchCard(){ - editText.getText().clear(); - logo.setVisibility(View.VISIBLE); - searchCard.setVisibility(View.VISIBLE); - overflowMenu.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.INVISIBLE); - loadingText.setVisibility(View.INVISIBLE); - loadingText.setText(R.string.loading); - progressBar.setIndeterminate(true); - } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); - private void prepForIntent(){ - progressBar.setIndeterminate(false); - progressBar.setProgress(100); - loadingText.setText(R.string.main_results_found); - searchInProgress = false; - } + if(Utils.isAdFree(this) != null && Utils.isAdFree(this)) { + menu.findItem(R.id.overflow_ad_free).setVisible(false); + } - private void goToSearchResults(HashMap searchResults, String entry){ - Intent intent = new Intent(getApplicationContext(), SearchResultsActivity.class); - intent.putExtra(SearchResultsActivity.EXTRA_RESULTS, searchResults); - intent.putExtra(SearchResultsActivity.EXTRA_SEARCHED,entry); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - prepForIntent(); + return true; } - private void goToDisplay(ArrayList conjugations){ - Intent intent = new Intent(getApplicationContext(), DisplayActivity.class); - intent.putExtra(DisplayActivity.EXTRA_CONJ, conjugations); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - prepForIntent(); + @Override + public void onResume(){ + super.onResume(); + this.searchCard.getSearchView().setQuery("", false); + this.searchCard.getSearchView().clearFocus(); + animationHandler.slideInViews(logo, linearLayout); } } \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/MessagingService.java b/app/src/main/java/com/a494studios/koreanconjugator/MessagingService.java index 64c38397..f294daf1 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/MessagingService.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/MessagingService.java @@ -6,7 +6,7 @@ import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import android.util.Log; import com.google.firebase.messaging.FirebaseMessagingService; diff --git a/app/src/main/java/com/a494studios/koreanconjugator/SearchActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/SearchActivity.java deleted file mode 100644 index e937e134..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/SearchActivity.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.a494studios.koreanconjugator; - -import android.app.SearchManager; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.PorterDuff; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.a494studios.koreanconjugator.parsing.Server; -import com.a494studios.koreanconjugator.utils.ErrorDialogFragment; -import com.crashlytics.android.Crashlytics; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdView; - -import java.util.ArrayList; -import java.util.HashMap; - -public class SearchActivity extends AppCompatActivity { - - ProgressBar progressBar; - TextView loadingText; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_search); - progressBar = findViewById(R.id.main_loadingBar); - loadingText = findViewById(R.id.main_loadingText); - AdView adView = findViewById(R.id.search_adView); - adView.loadAd(new AdRequest.Builder().build()); - - if (!Intent.ACTION_SEARCH.equals(getIntent().getAction())) { - this.onBackPressed(); - return; - } - - if(getIntent().getStringExtra(SearchManager.QUERY) == null){ - ErrorDialogFragment.newInstance().setListener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - onBackPressed(); - } - }).show(getSupportFragmentManager(),"error_dialog"); - Crashlytics.log("Query was null in SearchActivity"); - return; - } - final String entry = getIntent().getStringExtra(SearchManager.QUERY).trim(); - - if (getSupportActionBar() != null) getSupportActionBar().setTitle("Searching: " + entry); - progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN); - progressBar.getProgressDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN); - - // Search - if (Utils.isHangul(entry)) { - Crashlytics.log("Korean search: "+entry); - Crashlytics.setString("searchTerm",entry); - doKoreanSearch(entry); - } else if(entry.matches("[A-Za-z ]+")){ // Check if String in English - Crashlytics.log("English search: "+entry); - Crashlytics.setString("searchTerm",entry); - Server.requestEngDefinition(entry, getApplicationContext(), new Server.ServerListener() { - @Override - public void onResultReceived(ArrayList conjugations, HashMap searchResults) { - if(searchResults != null) { - if(searchResults.isEmpty()){ - AlertDialog dialog = new AlertDialog.Builder(SearchActivity.this) - .setTitle(R.string.no_results_title) - .setMessage(R.string.no_results_msg) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - onBackPressed(); - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - onBackPressed(); - } - }) - .create(); - if(!SearchActivity.this.isFinishing()) { - dialog.show(); - } - } else if (searchResults.size() == 1 || Utils.getEnglishLuck(getBaseContext())) { - doKoreanSearch(searchResults.keySet().iterator().next()); // Get the first key in map - } else { - goToSearchResults(searchResults,entry); - } - } - } - - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - }else{ - Crashlytics.log("Invalid Search: "+entry); - Crashlytics.setString("searchTerm",entry); - Toast.makeText(getBaseContext(),"Input not Valid",Toast.LENGTH_LONG).show(); - onBackPressed(); - } - } - - private void doKoreanSearch(final String entry) { - Server.requestKoreanSearch(entry, getApplicationContext(), new Server.ServerListener() { - @Override - public void onResultReceived(final ArrayList conjugations, HashMap searchResults) { - if (conjugations != null) { - goToDisplay(conjugations); - } else if (searchResults != null && !searchResults.isEmpty()) { - if(Utils.getKoreanLuck(getApplicationContext())){ - requestConjugations(searchResults.keySet().iterator().next()); - }else{ - goToSearchResults(searchResults,entry); - } - } else{ - AlertDialog dialog = new AlertDialog.Builder(SearchActivity.this) - .setTitle(R.string.no_results_title) - .setMessage(R.string.no_results_msg) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - onBackPressed(); - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - onBackPressed(); - } - }) - .create(); - if(!SearchActivity.this.isFinishing()) { - dialog.show(); - } - } - } - - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - } - - private void requestConjugations(String word){ - Server.requestConjugation(word, this, new Server.ServerListener() { - @Override - public void onResultReceived(ArrayList conjugations, HashMap searchResults) { - goToDisplay(conjugations); - } - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - } - - private void handleError(Exception error){ - Utils.handleError(error, this, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface,int i) { - onBackPressed(); // Go back to previous activity - } - }); - } - - @Override - public void onPause(){ - super.onPause(); - overridePendingTransition(0,0); - } - - private void prepForIntent(){ - progressBar.setIndeterminate(false); - progressBar.setProgress(100); - loadingText.setText(R.string.main_results_found); - } - - private void goToSearchResults(HashMap searchResults, String entry){ - Intent intent = new Intent(getApplicationContext(), SearchResultsActivity.class); - intent.putExtra(SearchResultsActivity.EXTRA_RESULTS, searchResults); - intent.putExtra(SearchResultsActivity.EXTRA_SEARCHED,entry); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - prepForIntent(); - } - - private void goToDisplay(ArrayList conjugations){ - Intent intent = new Intent(getApplicationContext(), DisplayActivity.class); - intent.putExtra(DisplayActivity.EXTRA_CONJ, conjugations); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - prepForIntent(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/SearchCard.java b/app/src/main/java/com/a494studios/koreanconjugator/SearchCard.java new file mode 100644 index 00000000..bc852499 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/SearchCard.java @@ -0,0 +1,62 @@ +package com.a494studios.koreanconjugator; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import androidx.appcompat.widget.SearchView; + +import com.a494studios.koreanconjugator.display.cards.DisplayCardBody; + +import static android.content.Context.SEARCH_SERVICE; + +public class SearchCard implements DisplayCardBody { + private View view; + private Activity activity; + private SearchView searchView; + + public SearchCard(Activity activity){ + this.activity = activity; + } + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_search,parentView); + } + searchView = view.findViewById(R.id.searchCard_search); + SearchManager searchManager = (SearchManager) context.getSystemService(SEARCH_SERVICE); + searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName())); + return view; + } + + public SearchView getSearchView(){ + return searchView; + } + + @Override + public void onButtonClick() { + // Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return 1; + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return null; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/SearchResultsActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/SearchResultsActivity.java deleted file mode 100644 index 5f97b1d4..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/SearchResultsActivity.java +++ /dev/null @@ -1,279 +0,0 @@ -package com.a494studios.koreanconjugator; - -import android.app.SearchManager; -import android.content.DialogInterface; -import android.content.Intent; -import android.support.design.widget.Snackbar; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; -import android.support.v7.widget.SearchView; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.a494studios.koreanconjugator.parsing.Conjugation; -import com.a494studios.koreanconjugator.parsing.Server; -import com.a494studios.koreanconjugator.settings.SettingsActivity; -import com.a494studios.koreanconjugator.utils.ErrorDialogFragment; -import com.android.volley.NoConnectionError; -import com.crashlytics.android.Crashlytics; -import com.eggheadgames.aboutbox.activity.AboutActivity; -import com.github.andkulikov.materialin.MaterialIn; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdView; - -import org.rm3l.maoni.Maoni; - -import java.util.ArrayList; -import java.util.HashMap; - -public class SearchResultsActivity extends AppCompatActivity { - - public static final String EXTRA_RESULTS = "RESULTS"; - public static final String EXTRA_SEARCHED = "SEARCHED"; - private static final String SAVED_RESULT_CONJS = "RESULT_CONJS"; - - private HashMap results; - private HashMap> resultConjs; - private SearchAdapter adapter; - private boolean snackbarShown; - private boolean overflowClicked; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_search_results); - ListView listView = findViewById(R.id.search_listView); - AdView adView = findViewById(R.id.search_results_adView); - snackbarShown = false; - if(savedInstanceState != null){ - results = (HashMap)savedInstanceState.getSerializable(EXTRA_RESULTS); - resultConjs = (HashMap>)savedInstanceState.getSerializable(SAVED_RESULT_CONJS); - }else { - results = (HashMap) getIntent().getSerializableExtra(EXTRA_RESULTS); - resultConjs = new HashMap<>(); - } - - if(results == null){ // Null check for extra - ErrorDialogFragment.newInstance().setListener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - onBackPressed(); - } - }).show(getSupportFragmentManager(),"error_dialog"); - Crashlytics.log("Results was null in SearchResultsActivity"); - return; - } - - adView.loadAd(new AdRequest.Builder().build()); - - ActionBar actionBar = getSupportActionBar(); - if(actionBar != null && getIntent().getStringExtra(EXTRA_SEARCHED) != null){ - actionBar.setTitle("Multiple results: "+getIntent().getStringExtra(EXTRA_SEARCHED)); - } - - adapter = new SearchAdapter(results); - listView.setAdapter(adapter); - requestData(); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - final String term = adapter.getKey(i); - ArrayList conjugations = resultConjs.get(term); - if(conjugations == null){ - Server.requestConjugation(term, getBaseContext(), new Server.ServerListener() { - @Override - public void onResultReceived(ArrayList conjugations, HashMap searchResults) { - sendIntent(conjugations,results.get(term)); - } - - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - }else { - sendIntent(conjugations,results.get(term)); - } - } - }); - } - - private void requestData(){ - for(final String key : results.keySet()){ - if(resultConjs.get(key) == null) { // No conjugation, so we have to request one. - Server.requestConjugation(key, this, new Server.ServerListener() { - @Override - public void onResultReceived(ArrayList conjugations, HashMap searchResults) { - resultConjs.put(key, conjugations); - } - - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - } - - if(results.get(key) == null || results.get(key).equals(getString(R.string.loading))){ // No definition, so we have to send a request for one. - results.put(key,getString(R.string.loading)); - Server.requestKorDefinition(key, this, new Server.DefinitionListener() { - @Override - public void onDefinitionReceived(String definition) { - results.put(key,definition); - adapter.notifyDataSetChanged(); - } - - @Override - public void onErrorOccurred(Exception error) { - handleError(error); - } - }); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.search_menu, menu); - SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); - SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); - searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == R.id.overflow_settings){ - startActivity(new Intent(getBaseContext(), SettingsActivity.class)); - return true; - }else if(item.getItemId() == R.id.overflow_about){ - Utils.makeAboutBox(this); - AboutActivity.launch(this); - return true; - }else if(item.getItemId() == R.id.overflow_bug) { - Maoni maoni = Utils.makeMaoniActivity(SearchResultsActivity.this); - if(maoni != null){ - maoni.start(SearchResultsActivity.this); - } - return true; - } else{ - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putSerializable(EXTRA_RESULTS, results); - savedInstanceState.putSerializable(SAVED_RESULT_CONJS,resultConjs); - super.onSaveInstanceState(savedInstanceState); - } - - @Override - public void onPause(){ - super.onPause(); - if(!overflowClicked) overridePendingTransition(0,0); - } - - @Override - public void onResume(){ - super.onResume(); - overflowClicked = false; - MaterialIn.animate(findViewById(R.id.search_listView), Gravity.BOTTOM, Gravity.BOTTOM); - } - - private void handleError(Exception error){ - if (!snackbarShown) { - Snackbar snackbar; - if (error instanceof NoConnectionError) { - snackbar = Snackbar.make(findViewById(R.id.search_listView), "Lost connection", Snackbar.LENGTH_INDEFINITE); - } else { - snackbar = Snackbar.make(findViewById(R.id.search_listView), "Couldn't connect to server", Snackbar.LENGTH_INDEFINITE); - System.err.println(error.toString()); - } - snackbar.setAction("Retry", new View.OnClickListener() { - @Override - public void onClick(View view) { - requestData(); - snackbarShown = false; - } - }); - snackbar.show(); - snackbarShown = true; - } - } - - private void sendIntent(ArrayList conjugations,String definition){ - Intent intent = new Intent(this,DisplayActivity.class); - if(definition.equals(getString(R.string.loading))){ - intent.putExtra(DisplayActivity.EXTRA_DEF,(String)null); - }else { - intent.putExtra(DisplayActivity.EXTRA_DEF, definition); - } - intent.putExtra(DisplayActivity.EXTRA_CONJ,conjugations); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - } -} - -class SearchAdapter extends BaseAdapter { - - private HashMap results; - private ArrayList keyList; - private static final int RESOURCE_ID = R.layout.item_conjugation; - - public SearchAdapter(HashMap results) { - this.results = results; - keyList = new ArrayList<>(results.keySet()); - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); - } - - String key = keyList.get(i); - String value = results.get(key); - TextView typeView = view.findViewById(R.id.conjFormal); - TextView conjView = view.findViewById(R.id.conjText); - typeView.setText(key); - conjView.setText(value); - return view; - } - - @Override - public int getCount() { - return results.size(); - } - - @Override - public Object getItem(int i) { - return results.get(keyList.get(i)); - } - - public String getKey(int i){ - return keyList.get(i); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/Utils.java b/app/src/main/java/com/a494studios/koreanconjugator/Utils.java deleted file mode 100644 index 061adc52..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/Utils.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.a494studios.koreanconjugator; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.support.v7.app.AppCompatActivity; - -import com.a494studios.koreanconjugator.parsing.Category; -import com.a494studios.koreanconjugator.parsing.EntrySerializer; -import com.a494studios.koreanconjugator.parsing.Form; -import com.a494studios.koreanconjugator.parsing.Formality; -import com.a494studios.koreanconjugator.parsing.Tense; -import com.a494studios.koreanconjugator.settings.LegalDisplayActivity; -import com.a494studios.koreanconjugator.utils.ErrorDialogFragment; -import com.a494studios.koreanconjugator.utils.SlackHandler; -import com.android.volley.NoConnectionError; -import com.android.volley.ParseError; -import com.crashlytics.android.Crashlytics; -import com.eggheadgames.aboutbox.AboutConfig; -import com.eggheadgames.aboutbox.IDialog; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import com.mikepenz.aboutlibraries.Libs; -import com.mikepenz.aboutlibraries.LibsBuilder; - -import org.rm3l.maoni.Maoni; - -import java.util.ArrayList; -import java.util.Map; - -import javax.annotation.Nullable; - -/** - * Created by akash on 1/9/2018. - */ - -public class Utils { - public static final String PREF_LUCKY_KOR = "pref_luckyKorean"; - public static final String PREF_LUCKY_ENG = "pref_luckyEnglish"; - public static final String PREF_FAV_COUNT = "pref_fav_count"; - private static final String PREF_FAV_VALUES = "FAVORITES_VALUES"; - private static final String PREF_FIRST_BOOT = "FIRST_BOOT"; - - public static boolean isFirstBoot(Context context){ - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_FIRST_BOOT,true); - } - - public static void setFirstBoot(Context context, boolean firstBoot){ - PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(PREF_FIRST_BOOT,firstBoot).apply(); - } - - public static boolean getKoreanLuck(Context context){ - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_LUCKY_KOR, false); - } - - public static boolean getEnglishLuck(Context context){ - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_LUCKY_ENG, false); - } - - public static int getFavCount(Context context){ - return PreferenceManager.getDefaultSharedPreferences(context).getInt(PREF_FAV_COUNT, 3); - } - - public static void setFavorites(ArrayList> data, Context context){ - Gson gson = new Gson(); - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); - editor.putString(PREF_FAV_VALUES,gson.toJson(data)); - editor.putInt(PREF_FAV_COUNT,data.size()); - editor.apply(); - } - - public static ArrayList> getFavorites(Context context) { - String jsonString = PreferenceManager.getDefaultSharedPreferences(context).getString(PREF_FAV_VALUES,""); - if(jsonString.isEmpty()){ - return new ArrayList<>(); - } - - GsonBuilder builder = new GsonBuilder(); - builder.registerTypeHierarchyAdapter(Map.Entry.class, new EntrySerializer()); - java.lang.reflect.Type type = new TypeToken>>(){}.getType(); - return builder.create().fromJson(jsonString,type); - } - - public static boolean isHangul(String korean){ - if(korean.isEmpty()){ - return false; - } - - korean = korean.replace(" ",""); - for(int i=0;i= '가' && (int)c <= '힣')){ - return false; - } - } - return true; - } - - public static Formality generateFormality(String type) { - if (type.contains(Formality.INFORMAL_LOW.toString())) { - return Formality.INFORMAL_LOW; - } else if (type.contains(Formality.INFORMAL_HIGH.toString())) { - return Formality.INFORMAL_HIGH; - } else if (type.contains(Formality.FORMAL_LOW.toString())) { - return Formality.FORMAL_LOW; - } else if (type.contains(Formality.FORMAL_HIGH.toString())) { - return Formality.FORMAL_HIGH; - } else if(type.contains(Formality.HONORIFIC_LOW.toString())) { - return Formality.HONORIFIC_LOW; - }else if(type.contains(Formality.HONORIFIC_HIGH.toString())){ - return Formality.HONORIFIC_HIGH; - }else { - return Formality.NONE; - } - } - - public static Tense generateTense(String type){ - if(type.contains(Tense.FUT_COND.toString())){ - return Tense.FUT_COND; - }else if(type.contains(Tense.FUTURE.toString())){ - return Tense.FUTURE; - }else if(type.contains(Tense.PRESENT.toString())){ - return Tense.PRESENT; - }else if(type.contains(Tense.PAST.toString())){ - return Tense.PAST; - }else{ - return Tense.NONE; - } - } - - public static Form generateForm(String type) { - if (type.contains(Form.DECLARATIVE.toString())) { - return Form.DECLARATIVE; - } else if (type.contains(Form.INQUISITIVE.toString())) { - return Form.INQUISITIVE; - } else if (type.contains(Form.IMPERATIVE.toString())) { - return Form.IMPERATIVE; - } else if (type.contains(Form.PROPOSITIVE.toString())) { - return Form.PROPOSITIVE; - } else if (type.contains(Form.CON_IF.toString())) { - return Form.CON_IF; - } else if (type.contains(Form.CON_AND.toString())) { - return Form.CON_AND; - } else if (type.contains(Form.CON_BUT.toString())){ - return Form.CON_BUT; - } else if (type.contains(Form.NOMINAL.toString())) { - return Form.NOMINAL; - } else if (type.contains(Form.PAST_BASE.toString())) { - return Form.PAST_BASE; - } else if (type.contains(Form.FUTURE_BASE.toString())) { - return Form.FUTURE_BASE; - }else if (type.contains(Form.ADJ.toString())) { - return Form.ADJ; - } else { - return Form.UNKNOWN; - } - } - - public static void makeAboutBox(final Activity activity){ - final AboutConfig aboutConfig = AboutConfig.getInstance(); - aboutConfig.appName = activity.getString(R.string.app_name); - aboutConfig.appIcon = R.mipmap.ic_launcher; - aboutConfig.version = BuildConfig.VERSION_NAME; - aboutConfig.author = "494 Studios"; - aboutConfig.aboutLabelTitle = "About App"; - aboutConfig.packageName = activity.getPackageName(); - aboutConfig.buildType = AboutConfig.BuildType.GOOGLE; - aboutConfig.appPublisher = "494 Studios"; // app publisher for "Try Other Apps" item - aboutConfig.privacyHtmlPath = "file:///android_asset/PrivacyPolicy.html"; - aboutConfig.acknowledgmentHtmlPath = "www.google.com"; - // Custom handler for Acknowledgements and Privacy Policy options - aboutConfig.dialog = new IDialog() { - @Override - public void open(AppCompatActivity appCompatActivity, String url, String tag) { - if(tag.equals(activity.getString(R.string.egab_privacy_policy))) { - Intent intent = new Intent(activity,LegalDisplayActivity.class); - intent.putExtra("type",LegalDisplayActivity.TYPE_PRIV_POLICY); - activity.startActivity(intent); - }else if(tag.equals(activity.getString(R.string.egab_acknowledgements))){ - new LibsBuilder() - .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR) - .withExcludedLibraries("support_cardview","support_v4","support_annotations","AppCompat","appcompat_v7","recyclerview_v7","GooglePlayServices","design","volleyplus") - .withLicenseDialog(true) - .withLicenseShown(true) - .withActivityTitle("Libraries Used") - .withLibraries("aboutBox","linear_list","transitions") - .start(activity); - } - } - }; - } - - @Nullable - public static Maoni makeMaoniActivity(AppCompatActivity context){ - SlackHandler listener = new SlackHandler(context); - if(!listener.auth()){ - displayErrorDialog(context,"Can't Connect to Server","Check your network settings and try again",null); - return null; - } - return new Maoni.Builder(context, "com.a494studios.koreanconjugator.fileprovider") - .enableScreenCapturingFeature() - .withLogsCapturingFeature(false) - .withHandler(listener) - .withExtraLayout(R.layout.activity_maoni_extra) - .withHeader(R.drawable.feedback_header) - .withTheme(R.style.AppTheme_NoActionBar) - .build(); - } - - public static void handleError(Exception error, AppCompatActivity context, DialogInterface.OnClickListener listener){ - ErrorDialogFragment fragment; - if(error instanceof NoConnectionError){ - fragment = ErrorDialogFragment.newInstance("Can't load results", - "Check your network settings and try again"); - } else if(error instanceof ParseError) { - fragment = ErrorDialogFragment.newInstance("Can't read results", - "A response was given that we couldn't understand"); - }else{ - Crashlytics.log("Unrecognized Error: "+ error.toString()); - fragment = ErrorDialogFragment.newInstance("Something went wrong", - "Try again later or contact support"); - } - - if(listener != null){ - fragment.setListener(listener); - } - context.getSupportFragmentManager() - .beginTransaction() - .add(fragment,"frag_alert") - .commitAllowingStateLoss(); - } - - public static void handleError(Exception error, AppCompatActivity context) { - handleError(error,context,null); - } - - public static void displayErrorDialog(AppCompatActivity context, String title, String msg,DialogInterface.OnClickListener listener){ - ErrorDialogFragment fragment = ErrorDialogFragment.newInstance(title, msg); - if(listener != null){ - fragment.setListener(listener); - } - - context.getSupportFragmentManager() - .beginTransaction() - .add(fragment,"frag_alert") - .commitAllowingStateLoss(); - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/WordOfDayCard.java b/app/src/main/java/com/a494studios/koreanconjugator/WordOfDayCard.java new file mode 100644 index 00000000..eb397f6c --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/WordOfDayCard.java @@ -0,0 +1,83 @@ +package com.a494studios.koreanconjugator; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import com.a494studios.koreanconjugator.display.DisplayActivity; +import com.a494studios.koreanconjugator.display.cards.DisplayCardBody; +import com.a494studios.koreanconjugator.parsing.Server; +import com.a494studios.koreanconjugator.utils.Utils; +import com.apollographql.apollo.api.Response; + +import io.reactivex.observers.DisposableObserver; + +public class WordOfDayCard implements DisplayCardBody { + private View view; + private String id; + + + @SuppressLint("CheckResult") + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_wod,parentView); + } + + Server.doWODQuery() + .subscribeWith(new DisposableObserver>() { + @Override + public void onNext(Response dataResponse) { + TextView textView = view.findViewById(R.id.wod_text); + textView.setText(dataResponse.data().wordOfTheDay.term); + + id = dataResponse.data().wordOfTheDay.id; + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + Utils.handleError(e, (AppCompatActivity) context, 8); + } + + @Override + public void onComplete() { + + } + }); + return view; + } + + @Override + public void onButtonClick() { + Intent intent = new Intent(view.getContext(), DisplayActivity.class); + intent.putExtra(DisplayActivity.EXTRA_ID, id); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + view.getContext().startActivity(intent); + } + + @Override + public boolean shouldHideButton() { + return false; + } + + @Override + public int getCount() { + return 1; + } + + @Override + public String getButtonText() { + return view.getContext().getString(R.string.see_entry); + } + + @Override + public String getHeading() { + return "Word of the Day"; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationActivity.java new file mode 100644 index 00000000..a726e156 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationActivity.java @@ -0,0 +1,114 @@ +package com.a494studios.koreanconjugator.conjugations; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.SwitchCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.parsing.Server; +import com.a494studios.koreanconjugator.utils.BaseActivity; +import com.a494studios.koreanconjugator.utils.Utils; + +import java.util.List; + +public class ConjugationActivity extends BaseActivity { + + public static final String EXTRA_STEM = "stem"; + public static final String EXTRA_HONORIFIC = "honorific"; + public static final String EXTRA_ISADJ = "isAdj"; + public static final String EXTRA_REGULAR = "regular"; + + private ConjugationAnimationHandler animationHandler; + private RecyclerView recyclerView; + private boolean dataLoaded = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conjugation); + + final String stem = getIntent().getStringExtra(EXTRA_STEM); + final boolean honorific = getIntent().getBooleanExtra(EXTRA_HONORIFIC,false); + final boolean isAdj = getIntent().getBooleanExtra(EXTRA_ISADJ,false); + final Boolean regular = (Boolean)getIntent().getSerializableExtra(EXTRA_REGULAR); + + ActionBar actionBar = getSupportActionBar(); + if(actionBar != null) { + actionBar.setTitle("Conjugations"); + actionBar.setElevation(0); + } + + setLoading(true); + getConjugations(stem, honorific, isAdj, regular); + TextView switchText = findViewById(R.id.conj_switchText); + ((SwitchCompat)findViewById(R.id.conj_switch)).setOnCheckedChangeListener((compoundBtn, checked) -> { + setLoading(true); + if(checked) { + switchText.setText(getString(R.string.honorific_forms)); + getConjugations(stem,true,isAdj, regular); + } else { + switchText.setText(getString(R.string.regular_forms)); + getConjugations(stem,false,isAdj, regular); + } + }); + + View extendedBar = findViewById(R.id.conj_switchBar); + recyclerView = findViewById(R.id.conj_list); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + + animationHandler = new ConjugationAnimationHandler(extendedBar,recyclerView,this); + animationHandler.setupScrollAnimations(layoutManager); + } + + @SuppressLint("CheckResult") + private void getConjugations(String stem, boolean honorific, boolean isAdj, Boolean regular) { + ConjugationObserver observer = new ConjugationObserver(new ConjugationObserver.ConjugationObserverListener() { + @Override + public void onDataReceived(List> conjugations) { + recyclerView.setAdapter(new ConjugationCardsAdapter(conjugations, stem, isAdj ? "Adjective" : "Verb")); + setLoading(false); + dataLoaded = true; + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + Utils.handleError(e, ConjugationActivity.this,4, + (dialogInterface, i) -> ConjugationActivity.this.onBackPressed()); + } + }); + + Server.doConjugationQuery(stem, honorific, isAdj, regular) + .subscribeWith(observer); + } + + @Override + public void onResume() { + super.onResume(); + animationHandler.animateListView(); + } + + private void setLoading(boolean loading){ + if(loading) { + findViewById(R.id.conj_progress).setVisibility(View.VISIBLE); + findViewById(R.id.conj_list).setVisibility(View.GONE); + } else { + findViewById(R.id.conj_progress).setVisibility(View.GONE); + findViewById(R.id.conj_list).setVisibility(View.VISIBLE); + + if(dataLoaded) { + animationHandler.slideInConjugations(); + } else { + animationHandler.animateListView(); + } + } + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationAnimationHandler.java b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationAnimationHandler.java new file mode 100644 index 00000000..86f8b87d --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationAnimationHandler.java @@ -0,0 +1,96 @@ +package com.a494studios.koreanconjugator.conjugations; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.utils.RecyclerAnimationHandler; + +public class ConjugationAnimationHandler extends RecyclerAnimationHandler { + + private View extendedBar; + private RecyclerView recyclerView; + + public ConjugationAnimationHandler(View extendedBar, RecyclerView recyclerView, Context context) { + super(extendedBar, recyclerView, context); + this.extendedBar = extendedBar; + this.recyclerView = recyclerView; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public void setupScrollAnimations(LinearLayoutManager layoutManager) { + final boolean[] isAnimating = {false}; + Animation.AnimationListener listener = new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + isAnimating[0] = true; + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimating[0] = false; + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }; + + GestureListener gestureListener = new GestureListener(isAnimating, layoutManager, listener); + GestureDetector detector = new GestureDetector(context, gestureListener); + recyclerView.setOnTouchListener((view, motionEvent) -> detector.onTouchEvent(motionEvent)); + } + + public void slideInConjugations() { + DecelerateInterpolator interpolator = new DecelerateInterpolator(2); + + recyclerView.setTranslationY(200); + recyclerView.animate().setInterpolator(interpolator).translationY(0); + } + + private class GestureListener extends GestureDetector.SimpleOnGestureListener { + boolean[] isAnimating; + LinearLayoutManager layoutManager; + Animation.AnimationListener listener; + + GestureListener(boolean[] isAnimating, LinearLayoutManager layoutManager, Animation.AnimationListener listener) { + this.isAnimating = isAnimating; + this.layoutManager = layoutManager; + this.listener = listener; + } + + @Override + public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float dx, float dy) { + if( (dx == 0 && dy == 0) || isAnimating[0] ){ + return false; // Bad event or animation already in progress + } + + int pos = layoutManager.findFirstCompletelyVisibleItemPosition(); + int visibility = extendedBar.getVisibility(); + if((dy < 0 || pos == 0) && visibility == View.INVISIBLE) { // Scroll up + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_in); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.VISIBLE); + } else if(dy > 0 && pos > 1 && visibility == View.VISIBLE ) { // Scroll down + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_out); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.INVISIBLE); + } + + return false; + } + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationCardsAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationCardsAdapter.java new file mode 100644 index 00000000..7f0af61c --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationCardsAdapter.java @@ -0,0 +1,59 @@ +package com.a494studios.koreanconjugator.conjugations; + +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.display.DisplayCardView; +import com.a494studios.koreanconjugator.display.cards.ConjugationCard; + +import java.util.List; +import java.util.Objects; + +public class ConjugationCardsAdapter extends RecyclerView.Adapter { + + private List> conjugations; + private String term; + private String pos; + + public ConjugationCardsAdapter(List> conjugations, String term, String pos) { + this.conjugations = Objects.requireNonNull(conjugations, "conjugations can't be null"); + this.term = Objects.requireNonNull(term, "term can't be null"); + this.pos = Objects.requireNonNull(pos, "pos can't be null"); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + DisplayCardView conjView = new DisplayCardView(parent.getContext()); + conjView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + return new ViewHolder(conjView); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + ConjugationCard card = new ConjugationCard(conjugations.get(position), term, pos); + holder.displayCardView.setCardBody(card); + } + + @Override + public int getItemCount() { + return conjugations.size(); + } + + public List getItem(int i) { + return conjugations.get(i); + } + + class ViewHolder extends RecyclerView.ViewHolder { + DisplayCardView displayCardView; + + ViewHolder(@NonNull DisplayCardView itemView) { + super(itemView); + displayCardView = itemView; + } + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationObserver.java b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationObserver.java new file mode 100644 index 00000000..9fd34ef3 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/conjugations/ConjugationObserver.java @@ -0,0 +1,59 @@ +package com.a494studios.koreanconjugator.conjugations; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.apollographql.apollo.api.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import io.reactivex.observers.DisposableObserver; + +public class ConjugationObserver extends DisposableObserver> { + private ConjugationObserverListener listener; + + public ConjugationObserver(ConjugationObserverListener listener) { + this.listener = listener; + } + + @Override + public void onNext(Response response) { + if(response.data() == null) { + return; + } + + List conjugations = response.data().conjugations(); + final TreeMap> conjMap = new TreeMap<>(); + for(ConjugationQuery.Conjugation conjugation : conjugations){ + String type = conjugation.type(); + if(conjMap.containsKey(type)){ + conjMap.get(type).add(conjugation); + }else{ + List value = new ArrayList<>(); + value.add(conjugation); + conjMap.put(type,value); + } + } + + List> sortedConj = new ArrayList<>(conjMap.values()); + listener.onDataReceived(sortedConj); + } + + @Override + public void onError(Throwable e) { + listener.onError(e); + } + + @Override + public void onComplete() { + + } + + + public interface ConjugationObserverListener { + void onDataReceived(List> conjugations); + + void onError(Throwable e); + } +} + diff --git a/app/src/main/java/com/a494studios/koreanconjugator/conjugator/ConjugatorActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/conjugator/ConjugatorActivity.java new file mode 100644 index 00000000..6424f895 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/conjugator/ConjugatorActivity.java @@ -0,0 +1,220 @@ +package com.a494studios.koreanconjugator.conjugator; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SwitchCompat; +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.Spinner; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.StemQuery; +import com.a494studios.koreanconjugator.conjugations.ConjugationCardsAdapter; +import com.a494studios.koreanconjugator.conjugations.ConjugationObserver; +import com.a494studios.koreanconjugator.parsing.Server; +import com.a494studios.koreanconjugator.utils.BaseActivity; +import com.a494studios.koreanconjugator.utils.Utils; +import com.apollographql.apollo.api.Response; + +import java.util.List; + +import io.reactivex.observers.DisposableObserver; + +public class ConjugatorActivity extends BaseActivity implements AdapterView.OnItemSelectedListener { + + public static final String EXTRA_TERM = "term"; + private static final int SPINNER_ITEM = R.layout.item_spinner; + + private RecyclerView recyclerView; + private LinearLayout linearLayout; + private ProgressBar loadingBar; + private SwitchCompat honorificSwitch; + private TextView honorificText; + private Spinner stemSpinner; + private Spinner posSpinner; + private Spinner regSpinner; + + private ConjugatorAnimationHandler animationHandler; + + // Used to prevent multiple refreshes for the same data + private boolean isRefreshing = false; + // Used to hide fields when first fetching possible stems + private boolean fetchingStems = false; + + @SuppressLint("CheckResult") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conjugator); + + final String term = getIntent().getStringExtra(EXTRA_TERM); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle("Conjugator"); + actionBar.setElevation(0); + } + + recyclerView = findViewById(R.id.conjugator_list); + linearLayout = findViewById(R.id.conjugator_linearLayout); + loadingBar = findViewById(R.id.conjugator_progress); + honorificSwitch = findViewById(R.id.conjugator_switch); + honorificText = findViewById(R.id.conjugator_switchText); + + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setNestedScrollingEnabled(true); + + NestedScrollView scrollView = findViewById(R.id.conjugator_scrollView); + View extendedBar = findViewById(R.id.conjugator_switchBar); + animationHandler = new ConjugatorAnimationHandler(extendedBar, scrollView, recyclerView, this); + animationHandler.setupScrollAnimations(layoutManager); + + stemSpinner = findViewById(R.id.conjugator_stemSpinner); + posSpinner = findViewById(R.id.conjugator_posSpinner); + regSpinner = findViewById(R.id.conjugator_regSpinner); + + ArrayAdapter posAdapter = ArrayAdapter.createFromResource(this, + R.array.pos, SPINNER_ITEM); + ArrayAdapter regAdapter = ArrayAdapter.createFromResource(this, + R.array.regularity, SPINNER_ITEM); + + posSpinner.setAdapter(posAdapter); + regSpinner.setAdapter(regAdapter); + + stemSpinner.setOnItemSelectedListener(this); + posSpinner.setOnItemSelectedListener(this); + regSpinner.setOnItemSelectedListener(this); + + fetchingStems = true; + setLoading(true); + + honorificSwitch.setOnCheckedChangeListener((compoundBtn, checked) -> { + setLoading(true); + if(checked) { + honorificText.setText(getString(R.string.honorific_forms)); + getConjugations(); + } else { + honorificText.setText(getString(R.string.regular_forms)); + getConjugations(); + } + }); + + Server.doStemQuery(term) + .subscribeWith(new DisposableObserver>() { + @Override + public void onNext(Response response) { + if (response.data() == null) { + return; + } + + List stems = response.data().stems(); + runOnUiThread(() -> { + ArrayAdapter adapter = + new ArrayAdapter<>(ConjugatorActivity.this, SPINNER_ITEM, stems); + stemSpinner.setAdapter(adapter); + stemSpinner.setEnabled(true); + + fetchingStems = false; + }); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + AppCompatActivity activity = ConjugatorActivity.this; + Utils.handleError(e, activity,11, + (dialogInterface, i) -> activity.onBackPressed()); + } + + @Override + public void onComplete() { + + } + }); + } + + @Override + public void onResume() { + super.onResume(); + animationHandler.animateListView(); + } + + @SuppressLint("CheckResult") + private void getConjugations() { + Object stemItem = stemSpinner.getSelectedItem(); + Object posItem = posSpinner.getSelectedItem(); + Object regItem = regSpinner.getSelectedItem(); + + // Already refreshing conjugations, wait for that to finish first, or not + // all spinners have finished being set up + if (isRefreshing || stemItem == null || posItem == null || regItem == null) { + return; + } + + isRefreshing = true; + setLoading(true); + + String stem = stemItem.toString(); + boolean isAdj = posItem.toString().equals("Adjective"); + boolean regular = regItem.toString().equals("Regular verb/adjective"); + boolean honorific = honorificSwitch.isChecked(); + + ConjugationObserver observer = new ConjugationObserver(new ConjugationObserver.ConjugationObserverListener() { + @Override + public void onDataReceived(List> conjugations) { + recyclerView.setAdapter(new ConjugationCardsAdapter(conjugations, stem, isAdj ? "Adjective" : "Verb")); + setLoading(false); + isRefreshing = false; + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + Utils.handleError(e, ConjugatorActivity.this,10, + (dialogInterface, i) -> ConjugatorActivity.this.onBackPressed()); + } + }); + + Server.doConjugationQuery(stem, honorific, isAdj, regular) + .subscribeWith(observer); + } + + private void setLoading(boolean loading){ + if(loading) { + loadingBar.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + + if(fetchingStems) { + linearLayout.setVisibility(View.INVISIBLE); + } + + } else { + loadingBar.setVisibility(View.GONE); + recyclerView.setVisibility(View.VISIBLE); + linearLayout.setVisibility(View.VISIBLE); + animationHandler.slideInConjugations(); + } + } + + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + getConjugations(); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/conjugator/ConjugatorAnimationHandler.java b/app/src/main/java/com/a494studios/koreanconjugator/conjugator/ConjugatorAnimationHandler.java new file mode 100644 index 00000000..7dbe5601 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/conjugator/ConjugatorAnimationHandler.java @@ -0,0 +1,72 @@ +package com.a494studios.koreanconjugator.conjugator; + +import android.content.Context; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.conjugations.ConjugationAnimationHandler; + +public class ConjugatorAnimationHandler extends ConjugationAnimationHandler { + + private View extendedBar; + private NestedScrollView scrollView; + + ConjugatorAnimationHandler(View extendedBar, NestedScrollView scrollView, + RecyclerView recyclerView, Context context) { + super(extendedBar, recyclerView, context); + + this.extendedBar = extendedBar; + this.scrollView = scrollView; + } + + @Override + public void setupScrollAnimations(LinearLayoutManager layoutManager) { + final boolean[] isAnimating = {false}; + Animation.AnimationListener listener = new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + isAnimating[0] = true; + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimating[0] = false; + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }; + + scrollView.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) + (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + double scrollViewHeight = scrollView.getChildAt(0).getBottom() + - scrollView.getHeight(); + double scrollPosition = ((double)scrollY/ scrollViewHeight) * 100d; + + int yDiff = scrollY - oldScrollY; + boolean isVisible = extendedBar.getVisibility() == View.VISIBLE; + + if (yDiff > 0 && scrollPosition > 10 && !isAnimating[0] && isVisible) { + // Scroll down, don't hide if at top of page + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_out); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.INVISIBLE); + } else if (yDiff < 0 && !isAnimating[0] && !isVisible) { + // Scroll up + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_in); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.VISIBLE); + } + }); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/ConjInfoActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/display/ConjInfoActivity.java new file mode 100644 index 00000000..4ce074ad --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/ConjInfoActivity.java @@ -0,0 +1,64 @@ +package com.a494studios.koreanconjugator.display; + +import androidx.appcompat.app.ActionBar; +import android.os.Bundle; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.display.cards.ConjInfoCard; +import com.a494studios.koreanconjugator.utils.BaseActivity; +import com.a494studios.koreanconjugator.utils.ScrollViewAnimationHandler; + +import java.util.List; + +public class ConjInfoActivity extends BaseActivity { + + public static final String EXTRA_NAME = "NAME"; + public static final String EXTRA_CONJ = "CONJ"; + public static final String EXTRA_PRON = "PRON"; + public static final String EXTRA_ROME = "ROME"; + public static final String EXTRA_EXPL = "EXPL"; + public static final String EXTRA_HONO = "HONO"; + private ScrollViewAnimationHandler animationHandler; + private View extendedBar; + private LinearLayout linearLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conj_info); + ActionBar actionBar = getSupportActionBar(); + if(actionBar != null) { + actionBar.setTitle(""); + actionBar.setElevation(0); + } + + String name = getIntent().getStringExtra(EXTRA_NAME); + String conjugated = getIntent().getStringExtra(EXTRA_CONJ); + String pronunciation = getIntent().getStringExtra(EXTRA_PRON); + String romanization = getIntent().getStringExtra(EXTRA_ROME); + List explanations = getIntent().getStringArrayListExtra(EXTRA_EXPL); + boolean isHonorific = getIntent().getBooleanExtra(EXTRA_HONO, false); + + DisplayCardView infoCard = findViewById(R.id.info_infoCard); + infoCard.setCardBody(new ConjInfoCard(name,conjugated,pronunciation,romanization,explanations)); + infoCard.showHonorificChip(isHonorific); + + CustomApplication.handleAdCard(findViewById(R.id.info_adCard), getString(R.string.CONJ_INFO_AD_ID)); + + extendedBar = findViewById(R.id.info_extendedBar); + linearLayout = findViewById(R.id.info_root); + ScrollView scrollView = findViewById(R.id.info_scroll); + animationHandler = new ScrollViewAnimationHandler(this, extendedBar, scrollView); + animationHandler.setupScrollAnimation(linearLayout); + } + + @Override + public void onResume() { + super.onResume(); + animationHandler.slideInViews(extendedBar, linearLayout); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayActivity.java new file mode 100644 index 00000000..cbdd5de8 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayActivity.java @@ -0,0 +1,149 @@ +package com.a494studios.koreanconjugator.display; + +import android.annotation.SuppressLint; +import androidx.appcompat.app.ActionBar; +import android.os.Bundle; + +import android.util.Pair; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.EntryQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.utils.BaseActivity; +import com.a494studios.koreanconjugator.utils.Logger; +import com.a494studios.koreanconjugator.utils.Utils; +import com.a494studios.koreanconjugator.parsing.Favorite; +import com.a494studios.koreanconjugator.parsing.Server; +import com.a494studios.koreanconjugator.utils.ScrollViewAnimationHandler; +import com.apollographql.apollo.api.Response; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; + +public class DisplayActivity extends BaseActivity { + + public static final String EXTRA_ID = "id"; + + private boolean isLoading; + private EntryQuery.Entry entry; + private ScrollViewAnimationHandler animationHandler; + + @SuppressLint("CheckResult") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_display); + String id = getIntent().getStringExtra(EXTRA_ID); + + // Make sure extras were passed + if(id == null){ + Exception exception = new Exception("ID was null in DisplayActivity"); + Utils.handleError(exception, this, 5, (dialogInterface, i) -> onBackPressed()); + return; + } + + ActionBar actionBar = getSupportActionBar(); + if(actionBar != null) { + actionBar.setTitle(""); + actionBar.setElevation(0); + } + + // Display progress bar until data is loaded + displayLoading(true); + + // Setting up Ad Card + CustomApplication.handleAdCard(findViewById(R.id.disp_adCard), getString(R.string.DISPLAY_AD_ID)); + + // Creating DisplayObserver + View rootView = findViewById(android.R.id.content); + DisplayObserver observer = new DisplayObserver(rootView, new DisplayObserver.DisplayObserverInterface() { + @Override + public void onError(Throwable t) { + t.printStackTrace(); + Utils.handleError(t, DisplayActivity.this,2, (dialogInterface, i) -> DisplayActivity.this.onBackPressed()); + } + + @Override + public void onComplete() { + displayLoading(false); + } + }); + + // Create Entry and Conjugations Observable + final ArrayList favorites = Utils.getFavorites(this); + ObservableSource observable = Server + .doEntryQuery(id) + .flatMap(dataResponse -> { + assert dataResponse.data() != null; + entry = dataResponse.data().entry(); + boolean isAdj = entry.pos().equals("Adjective"); + Boolean regular = entry.regular(); + observer.setEntry(entry); + + if (!entry.pos().equals("Verb") && !isAdj) { + return Observable.just(""); + } + + // Log select content event + Logger.getInstance().logSelectContent(entry.term(), entry.pos()); + + // Get favorite conjugation names and fetch them + List conjugations = Observable.fromIterable(favorites) + .map(Favorite::getConjugationName) + .toList() + .blockingGet(); + return Server.doConjugationQuery(entry.term(), false, isAdj, regular, conjugations); + }); + + // Combine with Examples Observable and execute + Server.doExamplesQuery(id) + .zipWith(observable, (examplesResponse, conjResponse) -> { + ConjugationQuery.Data conjData = null; + if(conjResponse instanceof Response) { + conjData = ((Response) conjResponse).data(); + } + + return new Pair<>(conjData, examplesResponse.data()); + }) + .subscribeWith(observer); + + LinearLayout linearLayout = findViewById(R.id.disp_root); + View extendedBar = findViewById(R.id.disp_extendedBar); + ScrollView scrollView = findViewById(R.id.disp_scroll); + animationHandler = new ScrollViewAnimationHandler(this, extendedBar, scrollView); + animationHandler.setupScrollAnimation(linearLayout); + } + + @Override + public void onResume() { + super.onResume(); + if (animationHandler != null) { + displayLoading(isLoading); + } + } + + private void displayLoading(boolean isLoading){ + this.isLoading = isLoading; + View progressBar = findViewById(R.id.disp_progress); + View extendedBar = findViewById(R.id.disp_extendedBar); + View rootLinearLayout = findViewById(R.id.disp_root); + if(isLoading){ + progressBar.setVisibility(View.VISIBLE); + extendedBar.setVisibility(View.INVISIBLE); + rootLinearLayout.setVisibility(View.GONE); + } else { + progressBar.setVisibility(View.GONE); + extendedBar.setVisibility(View.VISIBLE); + rootLinearLayout.setVisibility(View.VISIBLE); + + animationHandler.slideInViews(extendedBar,rootLinearLayout); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayCardView.java b/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayCardView.java new file mode 100644 index 00000000..e75f2fd3 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayCardView.java @@ -0,0 +1,101 @@ +package com.a494studios.koreanconjugator.display; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.display.cards.DisplayCardBody; + +import java.util.Objects; + +public class DisplayCardView extends RelativeLayout { + TextView headingView; + Button button; + DisplayCardBody cardBody; + Context context; + LinearLayout linearLayout; + TextView honorificChip; + + public DisplayCardView(Context context) { + super(context); + init(context); + } + + public DisplayCardView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public DisplayCardView(Context context, DisplayCardBody cardBody){ + super(context); + this.cardBody = Objects.requireNonNull(cardBody); + init(context); + } + + private void init(Context context){ + this.context = context; + + View rootView = inflate(context, R.layout.view_display_card,this); + headingView = rootView.findViewById(R.id.displayCard_heading); + button = rootView.findViewById(R.id.displayCard_button); + linearLayout = rootView.findViewById(R.id.displayCard_body); + honorificChip = rootView.findViewById(R.id.displayCard_honorific); + // Add body view + if(cardBody != null) { + cardBodyUpdates(); + } + } + + // pre: cardBody is not null + private void cardBodyUpdates() { + cardBody.addBodyView(context,linearLayout); // Add body view to linear layout + hideButton(cardBody.shouldHideButton()); + button.setText(cardBody.getButtonText()); + String heading = cardBody.getHeading(); + if(heading != null) { + headingView.setVisibility(VISIBLE); + headingView.setText(heading); + } else { + headingView.setVisibility(GONE); + } + setButtonListener(); + } + + private void setButtonListener() { + button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + cardBody.onButtonClick(); + cardBodyUpdates(); + } + }); + } + + public void setCardBody(DisplayCardBody cardBody) { + this.cardBody = Objects.requireNonNull(cardBody); + cardBodyUpdates(); + } + + public void showHonorificChip(boolean shouldShow) { + if(shouldShow) { + honorificChip.setVisibility(VISIBLE); + } else { + honorificChip.setVisibility(GONE); + } + } + + private void hideButton(boolean shouldHide){ + if(shouldHide){ + button.setVisibility(GONE); + linearLayout.setPadding(0,0,0,32); + } else { + button.setVisibility(VISIBLE); + linearLayout.setPadding(0,0,0,0); + } + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayObserver.java b/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayObserver.java new file mode 100644 index 00000000..a9408dde --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/DisplayObserver.java @@ -0,0 +1,133 @@ +package com.a494studios.koreanconjugator.display; + +import android.annotation.SuppressLint; +import android.util.Pair; +import android.view.View; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.EntryQuery; +import com.a494studios.koreanconjugator.ExamplesQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.utils.Utils; +import com.a494studios.koreanconjugator.display.cards.DefPOSCard; +import com.a494studios.koreanconjugator.display.cards.ExamplesCard; +import com.a494studios.koreanconjugator.display.cards.FavoritesCard; +import com.a494studios.koreanconjugator.display.cards.NoteCard; +import com.a494studios.koreanconjugator.display.cards.SynAntCard; +import com.a494studios.koreanconjugator.parsing.Favorite; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.reactivex.Observable; +import io.reactivex.observers.DisposableObserver; + +public class DisplayObserver extends DisposableObserver> { + private DisplayCardView displayCardView; + private DisplayCardView note; + private DisplayCardView examples; + private DisplayCardView synonyms; + private DisplayCardView antonyms; + private DisplayCardView conjugations; + + private EntryQuery.Entry entry; + private DisplayObserverInterface listener; + + DisplayObserver(View rootView, DisplayObserverInterface listener) { + this.displayCardView = rootView.findViewById(R.id.disp_dcv); + this.note = rootView.findViewById(R.id.disp_noteCard); + this.examples = rootView.findViewById(R.id.disp_examplesCard); + this.synonyms = rootView.findViewById(R.id.disp_synCard); + this.antonyms = rootView.findViewById(R.id.disp_antCard); + this.conjugations = rootView.findViewById(R.id.disp_conjCard); + this.listener = listener; + } + + public void setEntry(EntryQuery.Entry entry) { + this.entry = entry; + } + + @SuppressLint("CheckResult") + @Override + public void onNext(Pair response) { + ConjugationQuery.Data conjData = response.first; + ExamplesQuery.Data examplesData = response.second; + + // Favorites, skip if not a Verb or Adjective + if (conjData == null) { + conjugations.setVisibility(View.GONE); + } else { + List conjugations = conjData.conjugations(); + boolean isAdj = entry.pos().equals("Adjective"); + Boolean regular = entry.regular(); + List favorites = Utils.getFavorites(displayCardView.getContext()); + + FavoritesCard card = new FavoritesCard(new ArrayList<>(), entry.term(), false, isAdj, regular); + this.conjugations.setCardBody(card); + Observable.fromIterable(favorites) + .map(favorite -> { + // Pair up conjugations and favorites + for (ConjugationQuery.Conjugation c : conjugations) { + if (c.name().equals(favorite.getConjugationName())) { + return new Pair<>(favorite, c); + } + } + return null; + }) + .subscribe(pair -> { + Favorite f = pair.first; + ConjugationQuery.Conjugation conjugation = pair.second; + Map.Entry entry = + new AbstractMap.SimpleEntry<>(f.getName(), conjugation); + card.addConjugation(entry, favorites.indexOf(f)); + }); + } + + // Definitions and POS + displayCardView.setCardBody(new DefPOSCard(entry.term(),entry.pos(),entry.definitions())); + + // Note + if(entry.note() != null ) { + note.setCardBody(new NoteCard(entry.note())); + } else { + note.setVisibility(View.GONE); + } + + // Synonyms and Antonyms + if(entry.synonyms() != null) { + synonyms.setCardBody(new SynAntCard(entry.synonyms(),true)); + } else { + synonyms.setVisibility(View.GONE); + } + if(entry.antonyms() != null ) { + antonyms.setCardBody(new SynAntCard(entry.antonyms(),false)); + } else { + antonyms.setVisibility(View.GONE); + } + + // Examples + if(examplesData.examples() != null && !examplesData.examples().isEmpty()){ + examples.setCardBody(new ExamplesCard(examplesData.examples())); + } else { + examples.setVisibility(View.GONE); + } + } + + @Override + public void onError(Throwable e) { + listener.onError(e); + } + + @Override + public void onComplete() { + listener.onComplete(); + this.dispose(); + } + + public interface DisplayObserverInterface { + void onError(Throwable t); + void onComplete(); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ConjugationAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ConjugationAdapter.java new file mode 100644 index 00000000..0acb3dac --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ConjugationAdapter.java @@ -0,0 +1,65 @@ +package com.a494studios.koreanconjugator.display.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.type.SpeechLevel; + +import java.util.List; +import java.util.Objects; + +public class ConjugationAdapter extends BaseAdapter { + + private List conjugations; + private static final int RESOURCE_ID = R.layout.item_conjugation; + + public ConjugationAdapter(List conjugations) { + this.conjugations = Objects.requireNonNull(conjugations); + } + + @Override + public View getView(final int i, View view, ViewGroup viewGroup) { + if (view == null) { + view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); + } + ConjugationQuery.Conjugation c = conjugations.get(i); + TextView typeView = view.findViewById(R.id.conjFormal); + TextView conjView = view.findViewById(R.id.conjText); + + if(c.speechLevel() == SpeechLevel.NONE){ + typeView.setText(c.name()); + }else { + String speechLevel = c.speechLevel().toString(); + speechLevel = speechLevel.replace('_', ' ').toLowerCase(); + typeView.setText(speechLevel); + } + conjView.setText(c.conjugation()); + + return view; + } + + @Override + public int getCount() { + return conjugations.size(); + } + + @Override + public ConjugationQuery.Conjugation getItem(int i) { + return conjugations.get(i); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ExampleAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ExampleAdapter.java new file mode 100644 index 00000000..124bf600 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ExampleAdapter.java @@ -0,0 +1,51 @@ +package com.a494studios.koreanconjugator.display.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.ExamplesQuery; +import com.a494studios.koreanconjugator.R; + +import java.util.List; +import java.util.Objects; + +public class ExampleAdapter extends BaseAdapter { + private static final int RESOURCE_ID = R.layout.item_example; + private List examples; + + public ExampleAdapter(List examples){ + this.examples = Objects.requireNonNull(examples); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + if (view == null) { + view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); + } + + ExamplesQuery.Example example = examples.get(i); + TextView sentenceView = view.findViewById(R.id.item_example_sentence); + TextView transView = view.findViewById(R.id.item_example_translation); + sentenceView.setText(example.sentence()); + transView.setText(example.translation()); + return view; + } + + @Override + public int getCount() { + return examples.size(); + } + + @Override + public ExamplesQuery.Example getItem(int i) { + return examples.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ExplanationsAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ExplanationsAdapter.java new file mode 100644 index 00000000..4401a773 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/ExplanationsAdapter.java @@ -0,0 +1,58 @@ +package com.a494studios.koreanconjugator.display.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.R; + +import java.util.List; +import java.util.Objects; + +public class ExplanationsAdapter extends BaseAdapter { + private static final int RESOURCE_ID = R.layout.item_example; + private List explanations; + + public ExplanationsAdapter(List explanations){ + this.explanations = Objects.requireNonNull(explanations); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + if (view == null) { + view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); + } + + String explanation = explanations.get(i); + TextView sentenceView = view.findViewById(R.id.item_example_sentence); + TextView transView = view.findViewById(R.id.item_example_translation); + int index = explanation.indexOf('('); + if(index == -1){ + sentenceView.setText(explanation); + transView.setVisibility(View.GONE); + } else { + String header = explanation.substring(0, index).trim(); + String sub = explanation.substring(index).trim(); + sentenceView.setText(header); + transView.setText(sub); + } + return view; + } + + @Override + public int getCount() { + return explanations.size(); + } + + @Override + public String getItem(int i) { + return explanations.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/FavoritesAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/FavoritesAdapter.java new file mode 100644 index 00000000..fee65dea --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/adapters/FavoritesAdapter.java @@ -0,0 +1,66 @@ +package com.a494studios.koreanconjugator.display.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.R; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; + +public class FavoritesAdapter extends BaseAdapter { + + private ArrayList> entries; + private static final int RESOURCE_ID = R.layout.item_conjugation; + + public FavoritesAdapter(ArrayList> entries) { + this.entries = Objects.requireNonNull(entries); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + if (view == null) { + view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); + } + Map.Entry entry = entries.get(i); + TextView typeView = view.findViewById(R.id.conjFormal); + TextView conjView = view.findViewById(R.id.conjText); + typeView.setText(entry.getKey()); + conjView.setText(entry.getValue().conjugation()); + return view; + } + + public void addConjugation(Map.Entry entry, int index) { + if(index < entries.size() && index > 0) { + entries.add(index, entry); + } else { + entries.add(entry); + } + this.notifyDataSetChanged(); + } + + @Override + public int getCount() { + return entries.size(); + } + + @Override + public Object getItem(int i) { + return entries.get(i); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/AdCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/AdCard.java new file mode 100644 index 00000000..35617a21 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/AdCard.java @@ -0,0 +1,79 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.content.res.Resources; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.a494studios.koreanconjugator.R; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.AdView; + +public class AdCard implements DisplayCardBody { + private View view; + private String adUnitId; + + public AdCard(String adUnitId) { + this.adUnitId = adUnitId; + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_ad,parentView); + } + + AdView adView = new AdView(context); + adView.setAdSize(AdSize.MEDIUM_RECTANGLE); + adView.setAdUnitId(adUnitId); + + RelativeLayout container = view.findViewById(R.id.adCard); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + convertToPixels(250, context)); + params.gravity = Gravity.CENTER; + params.topMargin = convertToPixels(16, context); + params.bottomMargin = convertToPixels(16, context); + + adView.setLayoutParams(params); + container.addView(adView); + + adView.loadAd(new AdRequest.Builder().build()); + return view; + } + + @Override + public void onButtonClick() { + // Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return 1; + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return "Ad"; + } + + private int convertToPixels(int dp, Context context) { + Resources r = context.getResources(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ConjInfoCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ConjInfoCard.java new file mode 100644 index 00000000..1ea78def --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ConjInfoCard.java @@ -0,0 +1,73 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.utils.Utils; +import com.a494studios.koreanconjugator.display.adapters.ExplanationsAdapter; +import com.linearlistview.LinearListView; + +import java.util.List; +import java.util.Objects; + +public class ConjInfoCard implements DisplayCardBody { + + private View view; + private String name; + private String conjugated; + private String pronunciation; + private String romanization; + private List explanations; + + public ConjInfoCard(String name, String conjugated, String pronunciation, String romanization, List explanations) { + this.name = Utils.toTitleCase(Objects.requireNonNull(name)); + this.conjugated = Objects.requireNonNull(conjugated); + this.pronunciation = Objects.requireNonNull(pronunciation); + this.romanization = Objects.requireNonNull(romanization); + this.explanations = Objects.requireNonNull(explanations); + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_conj_info,parentView); + } + + ((TextView)view.findViewById(R.id.conjInfo_conjugated)).setText(conjugated); + ((TextView)view.findViewById(R.id.conjInfo_hpronc)).setText(pronunciation); + ((TextView)view.findViewById(R.id.conjInfo_roman)).setText(romanization); + + LinearListView listView = view.findViewById(R.id.conjInfo_explainList); + listView.setAdapter(new ExplanationsAdapter(explanations)); + return view; + } + + @Override + public void onButtonClick(){ + // Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return 1; + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return name; + } + +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ConjugationCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ConjugationCard.java new file mode 100644 index 00000000..c7eb8280 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ConjugationCard.java @@ -0,0 +1,92 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.view.ViewGroup; + +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.display.ConjInfoActivity; +import com.a494studios.koreanconjugator.display.adapters.ConjugationAdapter; +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.utils.Logger; +import com.a494studios.koreanconjugator.utils.Utils; +import com.linearlistview.LinearListView; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class ConjugationCard implements DisplayCardBody { + + private View view; + private String heading; + private String term; + private String pos; + private ConjugationAdapter adapter; + + public ConjugationCard(List conjugations, String term, String pos) { + this.adapter = new ConjugationAdapter(Objects.requireNonNull(conjugations)); + if (conjugations.isEmpty()) { + heading = "Conjugations"; + } else { + heading = Utils.toTitleCase(conjugations.get(0).type()); + } + this.term = term; + this.pos = pos; + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_list,parentView); + } + LinearListView listView = view.findViewById(R.id.listCard_list); + listView.setAdapter(adapter); + listView.setOnItemClickListener(new LinearListView.OnItemClickListener() { + @Override + public void onItemClick(LinearListView parent, View view, int position, long id) { + ConjugationQuery.Conjugation conjugation = adapter.getItem(position); + + // Log select conjugation event + Logger.getInstance().logSelectConjugation(term, pos, conjugation.name()); + + Intent i = new Intent(view.getContext(), ConjInfoActivity.class); + i.putExtra(ConjInfoActivity.EXTRA_NAME, conjugation.name()); + i.putExtra(ConjInfoActivity.EXTRA_CONJ,conjugation.conjugation()); + i.putExtra(ConjInfoActivity.EXTRA_PRON,conjugation.pronunciation()); + i.putExtra(ConjInfoActivity.EXTRA_ROME,conjugation.romanization()); + i.putExtra(ConjInfoActivity.EXTRA_EXPL,new ArrayList<>(conjugation.reasons())); + i.putExtra(ConjInfoActivity.EXTRA_HONO, conjugation.honorific()); + view.getContext().startActivity(i); + } + }); + return view; + } + + @Override + public void onButtonClick() { + // Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return adapter.getCount(); + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return heading; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/DefPOSCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/DefPOSCard.java new file mode 100644 index 00000000..8ed6467d --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/DefPOSCard.java @@ -0,0 +1,69 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import com.a494studios.koreanconjugator.utils.WordInfoView; + +import java.util.List; +import java.util.Objects; + +public class DefPOSCard implements DisplayCardBody { + + private String term; + private String pos; + private List definitions; + private WordInfoView view; + private String buttonText; + + public DefPOSCard(String term, String pos, List definitions) { + this.term = Objects.requireNonNull(term); + this.pos = Objects.requireNonNull(pos); + this.definitions = Objects.requireNonNull(definitions); + this.buttonText = (definitions.size() - 3) + " MORE"; + } + + @Override + public boolean shouldHideButton() { + return definitions.size() <= 3; // show if more than 3 definitions + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = new WordInfoView(context, term, pos, definitions, false); + } + + if(view.getParent() == null) { + parentView.addView(view); + } + return view; + } + + @Override + public void onButtonClick() { + boolean showingAll = view.getShowAll(); + if(showingAll) { + buttonText = (definitions.size() - 3) + " MORE"; + } else { + buttonText = "COLLAPSE"; + } + view.setShowAll(!showingAll); + } + + @Override + public int getCount() { + return definitions.size(); + } + + @Override + public String getButtonText() { + return buttonText; + } + + @Override + public String getHeading() { + return null; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/DisplayCardBody.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/DisplayCardBody.java new file mode 100644 index 00000000..1be3e3b5 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/DisplayCardBody.java @@ -0,0 +1,14 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +public interface DisplayCardBody { + View addBodyView(Context context, ViewGroup parentView); + void onButtonClick(); + boolean shouldHideButton(); + int getCount(); + String getButtonText(); + String getHeading(); +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ExamplesCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ExamplesCard.java new file mode 100644 index 00000000..15f4edde --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/ExamplesCard.java @@ -0,0 +1,58 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import com.a494studios.koreanconjugator.display.adapters.ExampleAdapter; +import com.a494studios.koreanconjugator.ExamplesQuery; +import com.a494studios.koreanconjugator.R; +import com.linearlistview.LinearListView; + +import java.util.List; +import java.util.Objects; + +public class ExamplesCard implements DisplayCardBody{ + private View view; + private ExampleAdapter adapter; + + public ExamplesCard(List examples) { + adapter = new ExampleAdapter(Objects.requireNonNull(examples)); + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_list,parentView); + } + + LinearListView listView = view.findViewById(R.id.listCard_list); + listView.setAdapter(adapter); + return view; + } + + @Override + public void onButtonClick() { + //Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return adapter.getCount(); + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return "Examples"; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/FavoritesCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/FavoritesCard.java new file mode 100644 index 00000000..e90424fd --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/FavoritesCard.java @@ -0,0 +1,81 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.view.ViewGroup; + +import com.a494studios.koreanconjugator.conjugations.ConjugationActivity; +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.display.adapters.FavoritesAdapter; +import com.linearlistview.LinearListView; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; + +public class FavoritesCard implements DisplayCardBody { + + private View view; + + private String stem; + private boolean honorific; + private boolean isAdj; + private Boolean regular; + private FavoritesAdapter adapter; + + public FavoritesCard(ArrayList> entries, String stem, boolean honorific, boolean isAdj, Boolean regular) { + this.adapter = new FavoritesAdapter(Objects.requireNonNull(entries)); + this.stem = Objects.requireNonNull(stem); + this.honorific = honorific; + this.isAdj = isAdj; + this.regular = regular; + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_list, parentView); + } + LinearListView listView = view.findViewById(R.id.listCard_list); + listView.setAdapter(adapter); + return view; + } + + @Override + public void onButtonClick() { + Intent i = new Intent(view.getContext(), ConjugationActivity.class); + i.putExtra(ConjugationActivity.EXTRA_STEM,stem); + i.putExtra(ConjugationActivity.EXTRA_HONORIFIC,honorific); + i.putExtra(ConjugationActivity.EXTRA_ISADJ, isAdj); + i.putExtra(ConjugationActivity.EXTRA_REGULAR, regular); + view.getContext().startActivity(i); + } + + @Override + public boolean shouldHideButton() { + return false; + } + + @Override + public int getCount() { + return adapter.getCount(); + } + + @Override + public String getButtonText() { + return "SEE ALL"; + } + + @Override + public String getHeading() { + return "Conjugations"; + } + + public void addConjugation(Map.Entry conjugation, int index) { + adapter.addConjugation(conjugation, index); + adapter.notifyDataSetChanged(); + } + +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/NoteCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/NoteCard.java new file mode 100644 index 00000000..7571202a --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/NoteCard.java @@ -0,0 +1,55 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.R; + +import java.util.Objects; + +public class NoteCard implements DisplayCardBody { + private View view; + private String note; + + public NoteCard(String note) { + this.note = Objects.requireNonNull(note); + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null){ + view = View.inflate(context, R.layout.dcard_simpletext,parentView); + } + + TextView textView = view.findViewById(R.id.simpleCard_text); + textView.setText(note); + return view; + } + + @Override + public void onButtonClick() { + // Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return 1; + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return "Note"; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/display/cards/SynAntCard.java b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/SynAntCard.java new file mode 100644 index 00000000..e789da6c --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/display/cards/SynAntCard.java @@ -0,0 +1,59 @@ +package com.a494studios.koreanconjugator.display.cards; + +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.R; + +import java.util.List; +import java.util.Objects; + +public class SynAntCard implements DisplayCardBody { + private View view; + private List wordList; + private boolean isSyn; + + public SynAntCard(List wordList, boolean isSyn) { + this.wordList = Objects.requireNonNull(wordList); + this.isSyn = isSyn; + } + + @Override + public View addBodyView(Context context, ViewGroup parentView) { + if(view == null) { + view = View.inflate(context, R.layout.dcard_simpletext,parentView); + } + + TextView textView = view.findViewById(R.id.simpleCard_text); + textView.setText(TextUtils.join(", ", wordList)); + return view; + } + + @Override + public void onButtonClick() { + // Empty on purpose + } + + @Override + public boolean shouldHideButton() { + return true; + } + + @Override + public int getCount() { + return wordList.size(); + } + + @Override + public String getButtonText() { + return "Button"; + } + + @Override + public String getHeading() { + return isSyn ? "Synonyms" : "Antonyms"; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Category.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Category.java deleted file mode 100644 index 575b5373..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Category.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.a494studios.koreanconjugator.parsing; - -import java.util.ArrayList; - -/** - * Created by akash on 1/2/2018. - */ - -public interface Category { - String printName(); - String getType(); - - class Categories{ - public static ArrayList getSubSet(ArrayList conjugations, Category... categories){ - ArrayList subset = new ArrayList<>(); - for(Conjugation c: conjugations){ - if(c.inCategories(categories)){ - subset.add(c); - } - } - return subset; - } - - public static ArrayList getSubSet(ArrayList conjugations, - Formality formality, Form form, Tense tense ){ - ArrayList subset = new ArrayList<>(); - for(Conjugation c: conjugations){ - if((formality == null || c.getFormality() == formality) && - (c.getForm() == form) && - (tense == null || c.getTense() == tense)){ - subset.add(c); - } - } - return subset; - } - - public static Category valueOf(String string){ - Tense t = Tense.fromString(string); - if(t != null){ - return t; - } - Form f = Form.fromString(string); - if(f != null){ - return f; - } - Formality fr = Formality.fromString(string); - if(fr != null){ - return fr; - } - return null; - } - - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Conjugation.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Conjugation.java deleted file mode 100644 index 9d011334..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Conjugation.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.a494studios.koreanconjugator.parsing; - -import com.a494studios.koreanconjugator.Utils; - -import java.io.Serializable; - -/** - * Created by akash on 12/31/2017. - */ - -public class Conjugation implements Serializable { - private String infinitive; - private String type; - private String conjugated; - private String pronunciation; - private String romanization; - private Formality formality; - private Form form; - private Tense tense; - - Conjugation(String infinitive, String type, String conjugated, String pronunciation, String romanization) { - this.infinitive = infinitive; - this.type = type; - this.conjugated = conjugated; - this.pronunciation = pronunciation; - this.romanization = romanization; - this.formality = Utils.generateFormality(type); - this.form = Utils.generateForm(type); - this.tense = Utils.generateTense(type); - } - - public boolean inCategories(Category... categories){ - for(Category c: categories){ - if(formality == c || form == c || tense == c ){ - return true; - } - } - return false; - } - - public String getInfinitive() { - return infinitive; - } - - public String getType() { - return type; - } - - public String getConjugated() { - return conjugated; - } - - public String getPronunciation() { - return pronunciation; - } - - public String getRomanization() { - return romanization; - } - - public Formality getFormality() { - return formality; - } - - public Form getForm() { - return form; - } - - public Tense getTense(){ - return tense; - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/EntrySerializer.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/EntrySerializer.java deleted file mode 100644 index 5b406d9a..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/EntrySerializer.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.a494studios.koreanconjugator.parsing; - -import com.crashlytics.android.Crashlytics; -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; - -import java.lang.reflect.Type; -import java.util.AbstractMap; -import java.util.Map; - -/** - * Created by akash on 1/13/2018. - */ - -public class EntrySerializer implements JsonDeserializer> { - @Override - public Map.Entry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - JsonObject jsonObject = json.getAsJsonObject(); - Crashlytics.log("Parsing Json Object: "+jsonObject.toString()); - String key = jsonObject.get("key").getAsString(); - Crashlytics.setString("Key",key); - JsonArray value = jsonObject.get("value").getAsJsonArray(); - Crashlytics.setString("value",value.toString()); - Category[] categories = new Category[3]; - for(int i =0;i(key,categories); - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Favorite.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Favorite.java new file mode 100644 index 00000000..0c853b13 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Favorite.java @@ -0,0 +1,29 @@ +package com.a494studios.koreanconjugator.parsing; + +public class Favorite { + private String name; + private String conjugationName; + private boolean honorific; + + Favorite() { + // require no-args constructor for gson + } + + public Favorite(String name, String conjugationName, boolean honorific) { + this.name = name; + this.conjugationName = conjugationName; + this.honorific = honorific; + } + + public String getName() { + return name; + } + + public String getConjugationName() { + return conjugationName; + } + + public boolean isHonorific() { + return honorific; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/FavoriteSerializer.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/FavoriteSerializer.java new file mode 100644 index 00000000..eac4924f --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/parsing/FavoriteSerializer.java @@ -0,0 +1,28 @@ +package com.a494studios.koreanconjugator.parsing; + +import com.google.firebase.crashlytics.FirebaseCrashlytics; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import java.lang.reflect.Type; + +/** + * Created by akash on 1/13/2018. + */ +//TODO reimplement Crashlytics +public class FavoriteSerializer implements JsonDeserializer { + @Override + public Favorite deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + FirebaseCrashlytics.getInstance().log("Parsing Json Object: "+jsonObject.toString()); + String name = jsonObject.get("name").getAsString(); + FirebaseCrashlytics.getInstance().setCustomKey("Name", name); + String conjugationName = jsonObject.get("conjugationName").getAsString(); + FirebaseCrashlytics.getInstance().setCustomKey("Conjugation Name", conjugationName); + boolean honorific = jsonObject.get("honorific").getAsBoolean(); + return new Favorite(name,conjugationName,honorific); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Form.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Form.java deleted file mode 100644 index 41af6244..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Form.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.a494studios.koreanconjugator.parsing; - -/** - * Created by akash on 1/1/2018. - */ - -public enum Form implements Category { - DECLARATIVE, INQUISITIVE, IMPERATIVE, PROPOSITIVE, CON_IF, CON_AND, CON_BUT, - NOMINAL, PAST_BASE, FUTURE_BASE, ADJ, UNKNOWN; - - @Override - public String printName() { - switch(this) { - case DECLARATIVE: return "declarative"; - case INQUISITIVE: return "inquisitive"; - case IMPERATIVE: return "imperative"; - case PROPOSITIVE: return "propositive"; - case CON_IF: return "connective if"; - case CON_AND: return "connective and"; - case CON_BUT: return "connective but"; - case NOMINAL: return "nominal"; - case PAST_BASE: return "past base"; - case FUTURE_BASE: return "future base"; - case ADJ: return "adjective"; - case UNKNOWN: return "unknown"; - default: throw new IllegalArgumentException(); - } - } - - public static Form fromString(String string) { - try{ - return valueOf(string); - }catch (IllegalArgumentException e){ - return null; - } - } - - @Override - public String toString(){ - return printName(); - } - - public String getType(){ - return super.toString(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Formality.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Formality.java deleted file mode 100644 index 3d9afcfd..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Formality.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.a494studios.koreanconjugator.parsing; - -/** - * Created by akash on 1/1/2018. - */ - -public enum Formality implements Category{ - INFORMAL_LOW, INFORMAL_HIGH, FORMAL_LOW, FORMAL_HIGH,HONORIFIC_LOW,HONORIFIC_HIGH,NONE; - - @Override - public String printName() { - switch(this) { - case INFORMAL_LOW: return "informal low"; - case INFORMAL_HIGH: return "informal high"; - case FORMAL_LOW: return "formal low"; - case FORMAL_HIGH: return "formal high"; - case HONORIFIC_LOW: return "honorific low"; - case HONORIFIC_HIGH:return "honorific high"; - case NONE: return "none"; - default: throw new IllegalArgumentException(); - } - } - - public static Formality fromString(String string) { - try{ - return valueOf(string); - }catch (IllegalArgumentException e){ - return null; - } - } - - @Override - public String toString(){ - return printName(); - } - - public String getType(){ - return super.toString(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Server.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Server.java index 538338bb..f8b3c01a 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Server.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Server.java @@ -1,190 +1,127 @@ package com.a494studios.koreanconjugator.parsing; -import android.content.Context; - -import com.android.volley.Request; -import com.android.volley.Response; -import com.android.volley.VolleyError; -import com.android.volley.toolbox.JsonArrayRequest; -import com.android.volley.toolbox.StringRequest; -import com.android.volley.toolbox.Volley; -import com.crashlytics.android.Crashlytics; -import com.google.common.net.UrlEscapers; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; +import com.a494studios.koreanconjugator.ConjugationNamesQuery; +import com.a494studios.koreanconjugator.ConjugationQuery; +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.EntryQuery; +import com.a494studios.koreanconjugator.ExamplesQuery; +import com.a494studios.koreanconjugator.SearchQuery; +import com.a494studios.koreanconjugator.StemQuery; +import com.a494studios.koreanconjugator.WordOfTheDayQuery; +import com.apollographql.apollo.ApolloQueryCall; +import com.apollographql.apollo.api.Response; +import com.apollographql.apollo.api.cache.http.HttpCachePolicy; +import com.apollographql.apollo.rx2.Rx2Apollo; + +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; /** * Created by akash on 12/31/2017. */ public class Server { - private static final String serverURL = com.a494studios.koreanconjugator.BuildConfig.SERVER_URL; - //private static final String serverURL = "http://localhost:8080/"; - private static final String conjURL = serverURL + "conjugate="; - private static final String searchKorURL = serverURL + "searchKor="; - private static final String defKorURL = serverURL + "defineKor="; - private static final String defEngURL = serverURL + "defineEng="; - private static final String stemURL = serverURL + "stem="; - - private static final String KEY_CONJ_INFIN = "infinitive"; - private static final String KEY_CONJ_TYPE = "conjugation_name"; - private static final String KEY_CONJ_CONJ = "conjugated"; - private static final String KEY_CONJ_PRONC = "pronunciation"; - private static final String KEY_CONJ_ROMAN = "romanized"; - private static final String KEY_SEARCH_WORD = "key"; - private static final String KEY_SEARCH_DEF = "def"; - - - public static void requestKoreanSearch(final String kword, final Context context, final ServerListener listener){ - String encoded = UrlEscapers.urlFragmentEscaper().escape(searchKorURL + kword);// Convert to %-encoding - JsonArrayRequest jsRequest = new JsonArrayRequest(Request.Method.GET, encoded, null, new Response.Listener() { - @Override - public void onResponse(JSONArray response) { - if(response.toString().contains("\"type\":")) { - ArrayList conjugations = parseConjugations(response); - if(conjugations == null){ - listener.onErrorOccurred(new JSONException("Something went wrong with JSON parsing")); - }else{ - listener.onResultReceived(conjugations,null); - } - }else if(response.toString().contains("\"key\":")){ - HashMap results = parseSearchResults(response); - if(results == null){ - listener.onErrorOccurred(new JSONException("Something went wrong with JSON parsing")); - }else{ - listener.onResultReceived(null,results); - } - } - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - Crashlytics.setString("requestType","koreanSearch"); - Crashlytics.setString("word",kword); - Crashlytics.logException(error); - listener.onErrorOccurred(error); - } - }); - Volley.newRequestQueue(context).add(jsRequest); + + public static Observable> doSearchQuery(final String query){ + return doSearchQuery(query, 0); } - public static void requestConjugation(final String kword, Context context, final ServerListener listener) { - String encoded = UrlEscapers.urlFragmentEscaper().escape(conjURL + kword);// Convert to %-encoding - JsonArrayRequest jsRequest = new JsonArrayRequest(Request.Method.GET, encoded, null, new Response.Listener() { - @Override - public void onResponse(JSONArray response) { - listener.onResultReceived(parseConjugations(response),null); - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - Crashlytics.setString("requestType","conjugate"); - Crashlytics.setString("word",kword); - Crashlytics.logException(error); - listener.onErrorOccurred(error); - } - }); - Volley.newRequestQueue(context).add(jsRequest); + public static Observable> doSearchQuery(String query, int cursor) { + SearchQuery.Builder queryBuilder = SearchQuery.builder() + .query(query) + .cursor(cursor); + + ApolloQueryCall call = CustomApplication.getApolloClient() + .query(queryBuilder.build()) + .httpCachePolicy(HttpCachePolicy.CACHE_FIRST); + + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse) -> dataResponse.data() != null); } - private static ArrayList parseConjugations(JSONArray response){ - try { - ArrayList conjugations = new ArrayList<>(); - for (int i = 0; i < response.length(); i++) { - JSONObject object = ((JSONObject) response.get(i)); - String infin = object.getString(KEY_CONJ_INFIN); - String type = object.getString(KEY_CONJ_TYPE); - String conj = object.getString(KEY_CONJ_CONJ); - String pronc = object.getString(KEY_CONJ_PRONC); - String roman = object.getString(KEY_CONJ_ROMAN); - conjugations.add(new Conjugation(infin, type, conj, pronc, roman)); - } - return conjugations; - } catch (JSONException e) { - Crashlytics.logException(e); - e.printStackTrace(); - } - return null; + public static Observable> doEntryQuery(final String id) { + EntryQuery query = EntryQuery.builder().id(id).build(); + ApolloQueryCall call = CustomApplication.getApolloClient().query(query); + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse) -> dataResponse.data() != null); + } + + public static Observable> doConjugationQuery(String stem, boolean honorific, boolean isAdj, Boolean regular){ + return doConjugationQuery(stem,honorific,isAdj,regular, null); } - private static HashMap parseSearchResults(JSONArray response){ - try { - HashMap results = new HashMap<>(); - for (int i = 0; i < response.length(); i++) { - JSONObject object = ((JSONObject) response.get(i)); - String key = object.getString(KEY_SEARCH_WORD); - String value = object.getString(KEY_SEARCH_DEF); - results.put(key,value); - } - return results; - } catch (JSONException e) { - Crashlytics.logException(e); - e.printStackTrace(); + public static Observable> doConjugationQuery(String stem, boolean honorific, boolean isAdj, Boolean regular, List conjugations){ + ConjugationQuery.Builder queryBuilder = ConjugationQuery.builder() + .stem(stem) + .honorific(honorific) + .isAdj(isAdj); + if(conjugations != null) { + queryBuilder.conjugations(conjugations); } - return null; + if(regular != null) { + queryBuilder.regular(regular); + } + + ApolloQueryCall call = CustomApplication.getApolloClient() + .query(queryBuilder.build()) + .httpCachePolicy(HttpCachePolicy.CACHE_FIRST); + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse) -> dataResponse.data() != null); } - public static void requestKorDefinition(final String word, Context context, final DefinitionListener listener) { - String encoded = UrlEscapers.urlFragmentEscaper().escape(defKorURL + word); // Convert to %-encoding - StringRequest jsRequest = new StringRequest(Request.Method.GET, encoded, new Response.Listener() { - @Override - public void onResponse(String response) { - listener.onDefinitionReceived(response); - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - Crashlytics.setString("requestType","koreanDefinition"); - Crashlytics.setString("word",word); - Crashlytics.logException(error); - listener.onErrorOccurred(error); - } - }); - Volley.newRequestQueue(context).add(jsRequest); + public static Observable> doExamplesQuery(final String id) { + ApolloQueryCall call = CustomApplication.getApolloClient() + .query(new ExamplesQuery(id)) + .httpCachePolicy(HttpCachePolicy.CACHE_FIRST); + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse) -> dataResponse.data() != null); + } - public static void requestEngDefinition(final String word, Context context, final ServerListener listener){ - String encoded = UrlEscapers.urlFragmentEscaper().escape(defEngURL + word); // Needed for spaces - JsonArrayRequest jsRequest = new JsonArrayRequest(Request.Method.GET, encoded, null,new Response.Listener() { - @Override - public void onResponse(JSONArray response) { - try { - HashMap entries = new HashMap<>(); - for (int i = 0; i < response.length(); i++) { - String key = response.getString(i); - entries.put(key,null); - } - listener.onResultReceived(null,entries); - }catch (JSONException e){ - e.printStackTrace(); - listener.onErrorOccurred(e); - } - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - Crashlytics.setString("requestType","engDefinition"); - Crashlytics.setString("word",word); - Crashlytics.logException(error); - listener.onErrorOccurred(error); - } - }); - Volley.newRequestQueue(context).add(jsRequest); + public static Observable> doConjugationNamesQuery() { + ApolloQueryCall call = CustomApplication.getApolloClient() + .query(new ConjugationNamesQuery()); + + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse -> dataResponse.data() != null)); } - public interface ServerListener { - void onResultReceived(ArrayList conjugations, HashMap searchResults); - void onErrorOccurred(Exception error); + public static Observable> doWODQuery() { + WordOfTheDayQuery.Builder queryBuilder = WordOfTheDayQuery.builder(); + + ApolloQueryCall call = CustomApplication.getApolloClient() + .query(queryBuilder.build()); + + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse -> dataResponse.data() != null)); } - public interface DefinitionListener { - void onDefinitionReceived(String definition); - void onErrorOccurred(Exception error); + public static Observable> doStemQuery(String term) { + StemQuery query = StemQuery.builder() + .term(term) + .build(); + + ApolloQueryCall call = CustomApplication.getApolloClient() + .query(query); + + return Rx2Apollo.from(call) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .filter((dataResponse -> dataResponse.data() != null)); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Tense.java b/app/src/main/java/com/a494studios/koreanconjugator/parsing/Tense.java deleted file mode 100644 index 01dc81af..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/parsing/Tense.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.a494studios.koreanconjugator.parsing; - -/** - * Created by akash on 1/1/2018. - */ - -public enum Tense implements Category{ - PAST, PRESENT, FUTURE, FUT_COND, NONE; - - @Override - public String printName() { - switch(this) { - case PAST: return "past"; - case PRESENT: return "present"; - case FUTURE: return "future"; - case FUT_COND: return "future conditional"; - case NONE: return "none"; - default: throw new IllegalArgumentException(); - } - } - - - public static Tense fromString(String string) { - try{ - return valueOf(string); - }catch (IllegalArgumentException e){ - return null; - } - } - - @Override - public String toString(){ - return printName(); - } - - public String getType(){ - return super.toString(); - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/search/NoResultsFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/search/NoResultsFragment.java new file mode 100644 index 00000000..d7391d34 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/search/NoResultsFragment.java @@ -0,0 +1,84 @@ +package com.a494studios.koreanconjugator.search; + + +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.conjugator.ConjugatorActivity; + +import org.jetbrains.annotations.NotNull; + +/** + * A simple {@link DialogFragment} subclass. + */ +public class NoResultsFragment extends DialogFragment implements DialogInterface.OnClickListener { + + private static final String ARG_SEARCH_TERM = "search_term"; + + private DialogInterface.OnClickListener onCancelListener = null; + + public NoResultsFragment() { + // Required empty public constructor + } + + static NoResultsFragment newInstance(String searchTerm, + DialogInterface.OnClickListener listener) { + NoResultsFragment frag = new NoResultsFragment(); + Bundle args = new Bundle(); + args.putString(ARG_SEARCH_TERM, searchTerm); + frag.setArguments(args); + frag.setOnCancelListener(listener); + return frag; + } + + @NotNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + String title = getString(R.string.no_results_title); + String msg = getString(R.string.no_results_msg); + String searchTerm = getArguments().getString(ARG_SEARCH_TERM); + + builder.setTitle(title); + builder.setMessage(msg); + + String cancelBtnText = getResources().getString(android.R.string.cancel); + if(onCancelListener != null) { + builder.setNegativeButton(cancelBtnText, onCancelListener); + } else { + builder.setNegativeButton(cancelBtnText,this); + } + + String okBtnText = getResources().getString(R.string.no_results_positive_btn); + builder.setPositiveButton(okBtnText, (dialogInterface, i) -> { + Intent intent = new Intent(getContext(), ConjugatorActivity.class); + intent.putExtra(ConjugatorActivity.EXTRA_TERM, searchTerm); + startActivity(intent); + }); + + return builder.create(); + } + + private void setOnCancelListener(DialogInterface.OnClickListener onCancelListener){ + this.onCancelListener = onCancelListener; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + //Empty on Purpose + } + + @Override + public void onDismiss(@NotNull DialogInterface dialog){ + super.onDismiss(dialog); + if (onCancelListener != null) { + onCancelListener.onClick(dialog, -1); + } + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/search/SearchActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/search/SearchActivity.java new file mode 100644 index 00000000..b838817b --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/search/SearchActivity.java @@ -0,0 +1,122 @@ +package com.a494studios.koreanconjugator.search; + +import android.annotation.SuppressLint; +import android.app.SearchManager; +import android.content.Intent; +import android.graphics.PorterDuff; +import androidx.appcompat.app.AppCompatActivity; +import android.os.Bundle; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.SearchQuery; +import com.a494studios.koreanconjugator.display.DisplayActivity; +import com.a494studios.koreanconjugator.parsing.Server; +import com.a494studios.koreanconjugator.search_results.SearchResultsActivity; +import com.a494studios.koreanconjugator.utils.ErrorDialogFragment; +import com.a494studios.koreanconjugator.utils.Logger; +import com.a494studios.koreanconjugator.utils.Utils; +import com.apollographql.apollo.api.Response; +import com.google.android.gms.ads.AdView; + +import java.util.List; + +import io.reactivex.observers.DisposableObserver; + +public class SearchActivity extends AppCompatActivity { + + ProgressBar progressBar; + TextView loadingText; + + @SuppressLint("CheckResult") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search); + progressBar = findViewById(R.id.main_loadingBar); + loadingText = findViewById(R.id.main_loadingText); + AdView adView = findViewById(R.id.search_adView); + CustomApplication.handleAdCard(adView); + + if (!Intent.ACTION_SEARCH.equals(getIntent().getAction())) { + finish(); + return; + } + + if(getIntent().getStringExtra(SearchManager.QUERY) == null){ + ErrorDialogFragment.newInstance() + .setListener((dialogInterface, i) -> finish()) + .show(getSupportFragmentManager(),"error_dialog"); + //Crashlytics.log("Query was null in SearchActivity"); + return; + } + final String entry = getIntent().getStringExtra(SearchManager.QUERY).trim(); + + if (getSupportActionBar() != null) getSupportActionBar().setTitle("Searching: " + entry); + progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN); + progressBar.getProgressDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), PorterDuff.Mode.SRC_IN); + + // Log search event + Logger.getInstance().logSearch(entry); + + // Search + Server.doSearchQuery(entry) + .subscribeWith(new DisposableObserver>() { + @Override + public void onNext(Response dataResponse) { + List results = dataResponse.data().search().results(); + if(results.isEmpty()) { + NoResultsFragment.newInstance(entry, (dialogInterface, i) -> finish()) + .show(getSupportFragmentManager(), "no_results_dialog"); + } else if(results.size() == 1){ + goToDisplay(results.get(0).id()); + } else { + goToSearchResults(entry); + } + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + Utils.handleError(e, SearchActivity.this, 1, (dialogInterface, i) -> SearchActivity.this.onBackPressed()); + } + + @Override + public void onComplete() { + this.dispose(); + } + }); + } + + @Override + public void onPause(){ + super.onPause(); + overridePendingTransition(0,0); + } + + private void prepForIntent(){ + this.runOnUiThread(() -> { + progressBar.setIndeterminate(false); + progressBar.setProgress(100); + loadingText.setText(R.string.main_results_found); + }); + } + + private void goToSearchResults(String query){ + Intent intent = new Intent(getApplicationContext(), SearchResultsActivity.class); + intent.putExtra(SearchResultsActivity.EXTRA_QUERY,query); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(intent); + prepForIntent(); + } + + private void goToDisplay(String id){ + Intent intent = new Intent(getApplicationContext(), DisplayActivity.class); + intent.putExtra(DisplayActivity.EXTRA_ID, id); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(intent); + prepForIntent(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/search_results/SearchResultsActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/search_results/SearchResultsActivity.java new file mode 100644 index 00000000..fb382cf2 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/search_results/SearchResultsActivity.java @@ -0,0 +1,148 @@ +package com.a494studios.koreanconjugator.search_results; + +import android.annotation.SuppressLint; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import android.os.Bundle; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.view.View; + +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.SearchQuery; +import com.a494studios.koreanconjugator.utils.BaseActivity; +import com.a494studios.koreanconjugator.parsing.Server; +import com.a494studios.koreanconjugator.utils.RecyclerAnimationHandler; +import com.a494studios.koreanconjugator.utils.Utils; +import com.apollographql.apollo.api.Response; +import com.google.android.gms.ads.AdView; + +import io.reactivex.observers.DisposableObserver; + +public class SearchResultsActivity extends BaseActivity { + + public static final String EXTRA_QUERY = "query"; + + private SearchResultsAdapter adapter; + private boolean snackbarShown; + private boolean overflowClicked; + private boolean dataLoaded = false; + private int cursor = 0; + private boolean loading = false; + private RecyclerAnimationHandler animationHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search_results); + RecyclerView recyclerView = findViewById(R.id.search_listView); + snackbarShown = false; + String query = getIntent().getStringExtra(EXTRA_QUERY); + if(query == null){ // Null check for extra + Exception exception = new Exception("Query was null in SearchResultsActivity"); + Utils.handleError(exception, this, 6, (dialogInterface, i) -> onBackPressed()); + return; + } + + CustomApplication.handleAdCard((AdView)findViewById(R.id.search_results_adView)); + + ActionBar actionBar = getSupportActionBar(); + if(actionBar != null){ + actionBar.setTitle("Results: "+ query); + actionBar.setElevation(0); + } + + final LinearLayoutManager layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + adapter = new SearchResultsAdapter(this) { + @Override + public void loadMore() { + fetchSearchResponse(query, cursor); + } + }; + recyclerView.setAdapter(adapter); + + // Pagination + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if(dx == 0 && dy == 0) { + return; // Bad event + } + + int pos = layoutManager.findLastVisibleItemPosition(); + int lastItemIndex = adapter.getItemCount() - 1; + + // Don't make a new request if the previous one is still loading + if (pos >= lastItemIndex && cursor != -1 && !loading) { + loading = true; + adapter.loadMore(); + } + } + }); + + View extendedBar = findViewById(R.id.search_results_extendedBar); + animationHandler = new RecyclerAnimationHandler(extendedBar, recyclerView, this); + animationHandler.setupScrollAnimations(layoutManager); + + // Checks made to handle screen rotation + if (cursor != -1 && !loading) { + loading = true; + fetchSearchResponse(query, cursor); + } + } + + @Override + public void onPause(){ + super.onPause(); + if(!overflowClicked) overridePendingTransition(0,0); + } + + @Override + public void onResume(){ + super.onResume(); + overflowClicked = false; + + if(dataLoaded) { + animationHandler.animateListView(); // Called when returning to this activity from another one + } + } + + @SuppressLint("CheckResult") + private void fetchSearchResponse(String query, int cur){ + Server.doSearchQuery(query, cur) + .subscribeWith(new DisposableObserver>() { + @Override + public void onNext(Response dataResponse) { + assert dataResponse.data() != null; // Check should be done in Server + adapter.addAll(dataResponse.data().search().results()); + String returnedCursor = dataResponse.data().search().cursor(); + if(returnedCursor != null) { + cursor = Integer.parseInt(dataResponse.data().search().cursor()); + } else { + cursor = -1; // No more results left to load + } + if(!dataLoaded) { + animationHandler.animateListView(); + dataLoaded = true; + } + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + Utils.handleError(e, SearchResultsActivity.this, 3, (dialogInterface, i) -> SearchResultsActivity.this.onBackPressed()); + } + + @Override + public void onComplete() { + loading = false; // Finished loading data for this request + this.dispose(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/search_results/SearchResultsAdapter.java b/app/src/main/java/com/a494studios/koreanconjugator/search_results/SearchResultsAdapter.java new file mode 100644 index 00000000..d12f83a3 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/search_results/SearchResultsAdapter.java @@ -0,0 +1,102 @@ +package com.a494studios.koreanconjugator.search_results; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.SearchQuery; +import com.a494studios.koreanconjugator.display.DisplayActivity; +import com.a494studios.koreanconjugator.utils.WordInfoView; + +import java.util.ArrayList; +import java.util.List; + +public abstract class SearchResultsAdapter extends RecyclerView.Adapter { + + private ArrayList results; + private Context context; + + SearchResultsAdapter(Context context) { + this.results = new ArrayList<>(); + this.context = context; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_search_result, parent, false); + + float elevation = view.getResources().getDimension(R.dimen.cardElevation); + ViewCompat.setElevation(view, elevation); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + ViewHolder viewHolder = (ViewHolder) holder; + final SearchQuery.Result result = results.get(position); + View.OnClickListener listener = view -> { + Intent intent = new Intent(context, DisplayActivity.class); + intent.putExtra(DisplayActivity.EXTRA_ID, result.id()); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + context.startActivity(intent); + }; + + + viewHolder.wordInfoView.setTerm(result.term()); + viewHolder.wordInfoView.setPos(result.pos()); + viewHolder.wordInfoView.setDefinitions(result.definitions()); + viewHolder.button.setText(R.string.see_entry); + viewHolder.button.setOnClickListener(listener); + viewHolder.itemView.setOnClickListener(listener); + + Resources resources = context.getResources(); + if(position == 0) { + Drawable drawable = resources.getDrawable(R.drawable.search_results_item_top); + viewHolder.itemView.setBackground(drawable); + } else if(position == getItemCount() - 1) { + Drawable drawable = resources.getDrawable(R.drawable.search_results_item_bottom); + viewHolder.itemView.setBackground(drawable); + } else { + Drawable drawable = resources.getDrawable(R.drawable.search_results_item_middle); + viewHolder.itemView.setBackground(drawable); + } + } + + @Override + public int getItemCount() { + return results.size(); + } + + void addAll(List results) { + int insertIndex = this.results.size() - 1; + this.results.addAll(results); + notifyItemRangeChanged(insertIndex ,results.size()); + } + + public abstract void loadMore(); + + private class ViewHolder extends RecyclerView.ViewHolder { + WordInfoView wordInfoView; + Button button; + + ViewHolder(@NonNull View itemView) { + super(itemView); + + wordInfoView = itemView.findViewById(R.id.item_search_result_word_info); + button = itemView.findViewById(R.id.item_search_result_button); + } + } + +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/settings/AddFavoriteFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/settings/AddFavoriteFragment.java index 458e9f27..d943d632 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/settings/AddFavoriteFragment.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/settings/AddFavoriteFragment.java @@ -4,27 +4,25 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.a494studios.koreanconjugator.R; -import com.a494studios.koreanconjugator.Utils; -import com.a494studios.koreanconjugator.parsing.Category; -import com.a494studios.koreanconjugator.parsing.Form; -import com.a494studios.koreanconjugator.parsing.Formality; -import com.a494studios.koreanconjugator.parsing.Tense; +import com.a494studios.koreanconjugator.parsing.Favorite; -import java.util.AbstractMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; /** * A simple {@link Fragment} subclass. @@ -35,13 +33,15 @@ * create an instance of this fragment. */ public class AddFavoriteFragment extends DialogFragment implements DialogInterface.OnClickListener { - public static final int ITEM_LAYOUT = R.layout.item_spinner; + public static final String ARG_NAMES = "NAMES"; + private static final int ITEM_LAYOUT = R.layout.item_spinner; private EditText nameEditText; - private Spinner formSpinner; - private Spinner formalitySpinner; - private Spinner tenseSpinner; + private Spinner conjSpinner; + private Spinner speechLevelSpinner; + private CheckBox honorificBox; private AddFavoriteFragmentListener mListener; + private HashMap conjData; public AddFavoriteFragment() { // Required empty public constructor @@ -53,64 +53,67 @@ public AddFavoriteFragment() { * * @return A new instance of fragment TimePickerFragment. */ - public static AddFavoriteFragment newInstance() { - return new AddFavoriteFragment(); + public static AddFavoriteFragment newInstance(HashMap data) { + AddFavoriteFragment frag = new AddFavoriteFragment(); + Bundle args = new Bundle(); + args.putSerializable(ARG_NAMES,data); + frag.setArguments(args); + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if(getArguments() != null) { + conjData = (HashMap)getArguments().getSerializable(ARG_NAMES); + } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + // Set up builder AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(R.layout.fragment_add_favorites); builder.setPositiveButton(getString(android.R.string.ok),this); builder.setNegativeButton(getString(android.R.string.cancel),this); + // Build and set up dialog view final AlertDialog dialog = builder.create(); dialog.show(); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - formalitySpinner = dialog.findViewById(R.id.addFav_formalitySpinner); - formSpinner = dialog.findViewById(R.id.addFav_formSpinner); - tenseSpinner = dialog.findViewById(R.id.addFav_tenseSpinner); + speechLevelSpinner = dialog.findViewById(R.id.addFav_speechLevelSpinner); + conjSpinner = dialog.findViewById(R.id.addFav_conjSpinner); nameEditText = dialog.findViewById(R.id.addFav_name); + honorificBox = dialog.findViewById(R.id.addFav_checkbox); - formalitySpinner.setEnabled(false); - tenseSpinner.setEnabled(false); + // Set up speechLevel spinner + speechLevelSpinner.setAdapter(ArrayAdapter.createFromResource(getContext(), R.array.formality, ITEM_LAYOUT)); + speechLevelSpinner.setVisibility(View.GONE); + speechLevelSpinner.setEnabled(false); - final ArrayAdapter adapter = ArrayAdapter.createFromResource(getContext(), R.array.forms, ITEM_LAYOUT); + // Set up conj spinner + ArrayList conjNames = new ArrayList<>(conjData.keySet()); + Collections.sort(conjNames); // sort conjugations alphabetically + final ArrayAdapter adapter = new ArrayAdapter<>(getContext(),ITEM_LAYOUT,conjNames); adapter.setDropDownViewResource(ITEM_LAYOUT); - formSpinner.setAdapter(adapter); - formSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + conjSpinner.setAdapter(adapter); + conjSpinner.setEnabled(true); + + conjSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - String selection = adapter.getItem(i).toString().toLowerCase(); + String selection = adapter.getItem(i); if(!nameEditText.getText().toString().isEmpty()){ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); } - if(selection.equals("select a form…")){ - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - } - - formalitySpinner.setEnabled(true); - tenseSpinner.setEnabled(true); - formalitySpinner.setAdapter(ArrayAdapter.createFromResource(getContext(), - R.array.formality, ITEM_LAYOUT)); - if(selection.equals(Form.DECLARATIVE.toString())){ - tenseSpinner.setAdapter(ArrayAdapter.createFromResource(getContext(), - R.array.tense_dec, ITEM_LAYOUT)); - }else if(selection.equals(Form.INQUISITIVE.toString())){ - tenseSpinner.setAdapter(ArrayAdapter.createFromResource(getContext(), - R.array.tense_inq, ITEM_LAYOUT)); - }else if(selection.equals(Form.IMPERATIVE.toString()) - || selection.equals(Form.PROPOSITIVE.toString())){ - tenseSpinner.setAdapter(ArrayAdapter.createFromResource(getContext(), - R.array.tense_imp_prop, ITEM_LAYOUT)); - }else{ - ArrayAdapter emptyAdapter = new ArrayAdapter<>(getContext(), ITEM_LAYOUT,new String[0]); - formalitySpinner.setEnabled(false); - formalitySpinner.setAdapter(emptyAdapter); - tenseSpinner.setEnabled(false); - tenseSpinner.setAdapter(emptyAdapter); + boolean hasSpeechLevel = conjData.get(selection); + speechLevelSpinner.setEnabled(hasSpeechLevel); + if(hasSpeechLevel) { + speechLevelSpinner.setVisibility(View.VISIBLE); + } else { + speechLevelSpinner.setVisibility(View.GONE); } } @@ -128,7 +131,7 @@ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - if(!nameEditText.getText().toString().isEmpty() && !formSpinner.getSelectedItem().toString().equals("Select a Form…")){ + if(!nameEditText.getText().toString().isEmpty() && !conjSpinner.getSelectedItem().toString().equals("Select a Form…")){ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); }else{ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); @@ -164,25 +167,19 @@ public void onDetach() { public void onClick(DialogInterface dialog, int which) { if(which == DialogInterface.BUTTON_NEGATIVE){ dialog.dismiss(); - return; - } - - if(nameEditText.getText().toString().isEmpty() || formSpinner.getSelectedItem().toString().equals("Select a Form…")){ - Toast.makeText(getContext(),"Invalid input",Toast.LENGTH_LONG).show(); - return; - } - - - String name = nameEditText.getText().toString(); - Form form = Utils.generateForm(formSpinner.getSelectedItem().toString().toLowerCase()); - Formality formality = null; - Tense tense = null; - if(!(form == Form.NOMINAL || form == Form.CON_AND || form == Form.CON_IF)){ - tense = Utils.generateTense(tenseSpinner.getSelectedItem().toString().toLowerCase()); - formality = Utils.generateFormality(formalitySpinner.getSelectedItem().toString().toLowerCase()); + } else { + if (nameEditText.getText().toString().isEmpty()) { + Toast.makeText(getContext(), "Invalid input", Toast.LENGTH_LONG).show(); + } else { + String name = nameEditText.getText().toString(); + String conjName = conjSpinner.getSelectedItem().toString().trim(); + if(conjData.get(conjName)) { + conjName += " " + speechLevelSpinner.getSelectedItem().toString().trim(); + } + boolean honorific = honorificBox.isChecked(); + mListener.onFavoriteAdded(new Favorite(name,conjName.toLowerCase(),honorific)); + } } - Category[] categories = {formality,form,tense}; - mListener.onFavoriteAdded(new AbstractMap.SimpleEntry<>(name,categories)); } @@ -197,6 +194,6 @@ public void onClick(DialogInterface dialog, int which) { * >Communicating with Other Fragments for more information. */ public interface AddFavoriteFragmentListener { - void onFavoriteAdded(Map.Entry entry); + void onFavoriteAdded(Favorite entry); } } diff --git a/app/src/main/java/com/a494studios/koreanconjugator/settings/FavoritesActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/settings/FavoritesActivity.java index 9844ddc4..b213fc91 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/settings/FavoritesActivity.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/settings/FavoritesActivity.java @@ -1,10 +1,11 @@ package com.a494studios.koreanconjugator.settings; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; + +import android.annotation.SuppressLint; import android.os.Bundle; import android.view.ContextMenu; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; @@ -14,20 +15,27 @@ import android.widget.ListView; import android.widget.TextView; +import com.a494studios.koreanconjugator.ConjugationNamesQuery; import com.a494studios.koreanconjugator.R; -import com.a494studios.koreanconjugator.Utils; -import com.a494studios.koreanconjugator.parsing.Category; +import com.a494studios.koreanconjugator.utils.Logger; +import com.a494studios.koreanconjugator.utils.Utils; +import com.a494studios.koreanconjugator.parsing.Favorite; +import com.a494studios.koreanconjugator.parsing.Server; +import com.apollographql.apollo.api.Response; -import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Map; +import java.util.HashMap; +import java.util.List; + +import io.reactivex.observers.DisposableObserver; -public class FavoritesActivity extends AppCompatActivity implements AddFavoriteFragment.AddFavoriteFragmentListener - ,RenameFavoriteFragment.RenameFavoriteFragmentListener{ +public class FavoritesActivity extends AppCompatActivity implements AddFavoriteFragment.AddFavoriteFragmentListener { FavoritesAdapter adapter; + AddFavoriteFragment addFavoriteFragment; + @SuppressLint("CheckResult") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -40,6 +48,53 @@ protected void onCreate(Bundle savedInstanceState) { ListView listView = findViewById(R.id.fav_listView); adapter = new FavoritesAdapter(Utils.getFavorites(this)); listView.setAdapter(adapter); + + Server.doConjugationNamesQuery() + .subscribeWith(new DisposableObserver>() { + @Override + public void onNext(Response response) { + if (response.data() == null) { + return; + } + + List names = response.data().conjugationNames(); + HashMap data = new HashMap<>(); + for (String name : names) { + name = name.toLowerCase(); + boolean showSpeechLevels = false; + if (name.contains("informal low")) { + showSpeechLevels = true; + name = name.replace("informal low", ""); + } else if (name.contains("informal high")) { + showSpeechLevels = true; + name = name.replace("informal high", ""); + } else if (name.contains("formal low")) { + showSpeechLevels = true; + name = name.replace("formal low", ""); + } else if (name.contains("formal high")) { + showSpeechLevels = true; + name = name.replace("formal high", ""); + } + + if (!data.containsKey(name)) { + data.put(Utils.toTitleCase(name), showSpeechLevels); + } + } + addFavoriteFragment = AddFavoriteFragment.newInstance(data); + } + + @Override + public void onError(Throwable e) { + Utils.handleError(e, FavoritesActivity.this, 9); + } + + @Override + public void onComplete() { + + } + }); + + listView.setAdapter(adapter); registerForContextMenu(listView); } @@ -54,32 +109,18 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); if( item.getItemId() == R.id.context_delete) { - ArrayList> data = adapter.remove(adapter.getItem(info.position)); - Utils.setFavorites(data,this); + ArrayList favorites = adapter.remove(adapter.getItem(info.position)); + Utils.setFavorites(favorites,this); adapter.notifyDataSetChanged(); return true; - }else if(item.getItemId() == R.id.context_rename) { - RenameFavoriteFragment frag = RenameFavoriteFragment.newInstance(info.position); - frag.show(getSupportFragmentManager(),"rename_frag"); - return true; }else{ return super.onContextItemSelected(item); } } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.fav_menu, menu); - return true; - } - @Override public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == R.id.menu_add) { - AddFavoriteFragment.newInstance().show(getSupportFragmentManager(),"add_fav_frag"); - return true; - }else if(item.getItemId() == android.R.id.home) { + if(item.getItemId() == android.R.id.home) { this.onBackPressed(); return true; }else{ @@ -88,27 +129,24 @@ public boolean onOptionsItemSelected(MenuItem item) { } @Override - public void onFavoriteAdded(Map.Entry entry) { - ArrayList> data = adapter.add(entry); - Utils.setFavorites(data,this); + public void onFavoriteAdded(Favorite entry) { + Logger.getInstance().logFavoriteAdded(entry.getName(), entry.getConjugationName(), entry.isHonorific()); + ArrayList favorites = adapter.add(entry); + Utils.setFavorites(favorites,this); adapter.notifyDataSetChanged(); } - @Override - public void onRenameSelected(String newName, int position) { - Map.Entry entry = adapter.getItem(position); - Map.Entry newEntry = new AbstractMap.SimpleEntry<>(newName, entry.getValue()); - ArrayList> data = adapter.replace(entry, newEntry); - Utils.setFavorites(data, this); - adapter.notifyDataSetChanged(); + // FAB's onClick listener + public void onAddFavorite(View view) { + addFavoriteFragment.show(getSupportFragmentManager(),"add_fav_frag"); } private class FavoritesAdapter extends BaseAdapter { - private ArrayList> entries; + private ArrayList entries; private static final int RESOURCE_ID = R.layout.item_setting_fav; - public FavoritesAdapter(ArrayList> entries) { + FavoritesAdapter(ArrayList entries) { this.entries = entries; } @@ -117,24 +155,14 @@ public View getView(int i, View view, ViewGroup viewGroup) { if (view == null) { view = LayoutInflater.from(viewGroup.getContext()).inflate(RESOURCE_ID, viewGroup, false); } - Map.Entry entry = entries.get(i); - Category[] categories = entry.getValue(); - TextView typeView = view.findViewById(R.id.fav_title); - TextView formView = view.findViewById(R.id.fav_form); - TextView formalityView = view.findViewById(R.id.fav_formality); - TextView tenseView = view.findViewById(R.id.fav_tense); - - typeView.setText(entry.getKey()); - formView.setText(categories[1].printName()); - if(categories[0] != null) { - formalityView.setText(categories[0].printName()); - }else{ - formalityView.setText(""); - } - if(categories[2] != null) { - tenseView.setText(categories[2].printName()); - }else{ - tenseView.setText(""); + + Favorite favorite = entries.get(i); + ((TextView)view.findViewById(R.id.item_fav_title)).setText(favorite.getName()); + ((TextView)view.findViewById(R.id.item_fav_subtitle)).setText(Utils.toTitleCase(favorite.getConjugationName())); + if(favorite.isHonorific()) { + view.findViewById(R.id.item_fav_honorific).setVisibility(View.VISIBLE); + } else { + view.findViewById(R.id.item_fav_honorific).setVisibility(View.GONE); } return view; } @@ -145,7 +173,7 @@ public int getCount() { } @Override - public Map.Entry getItem(int i) { + public Favorite getItem(int i) { return entries.get(i); } @@ -159,19 +187,14 @@ public boolean hasStableIds() { return true; } - public ArrayList> remove(Map.Entry entry){ + public ArrayList remove(Favorite entry){ entries.remove(entry); return entries; } - public ArrayList> add(Map.Entry entry){ + public ArrayList add(Favorite entry){ entries.add(entry); return entries; } - - public ArrayList> replace(Map.Entry old, Map.Entry newEntry){ - entries.set(entries.indexOf(old),newEntry); - return entries; - } } } \ No newline at end of file diff --git a/app/src/main/java/com/a494studios/koreanconjugator/settings/LegalDisplayActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/settings/LegalDisplayActivity.java index 06248110..68f2d8ee 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/settings/LegalDisplayActivity.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/settings/LegalDisplayActivity.java @@ -2,13 +2,13 @@ import android.content.DialogInterface; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.view.MenuItem; import android.webkit.WebView; import com.a494studios.koreanconjugator.R; import com.a494studios.koreanconjugator.utils.ErrorDialogFragment; -import com.crashlytics.android.Crashlytics; +import com.a494studios.koreanconjugator.utils.Utils; public class LegalDisplayActivity extends AppCompatActivity { @@ -27,13 +27,8 @@ protected void onCreate(Bundle savedInstanceState) { WebView wv = findViewById(R.id.webview); String type = getIntent().getStringExtra("type"); if(type == null){ // Null check for extra - ErrorDialogFragment.newInstance().setListener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - onBackPressed(); - } - }).show(getSupportFragmentManager(),"error_dialog"); - Crashlytics.log("Type was null in LegalDisplayActivity"); + Exception exception = new Exception("Type was null in LegalDisplayActivity"); + Utils.handleError(exception, this, 7, (dialogInterface, i) -> onBackPressed()); }else { switch (type) { case TYPE_PRIV_POLICY: diff --git a/app/src/main/java/com/a494studios/koreanconjugator/settings/RenameFavoriteFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/settings/RenameFavoriteFragment.java deleted file mode 100644 index 44218721..00000000 --- a/app/src/main/java/com/a494studios/koreanconjugator/settings/RenameFavoriteFragment.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.a494studios.koreanconjugator.settings; - -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; -import android.widget.EditText; - -import com.a494studios.koreanconjugator.R; - -/** - * A simple {@link Fragment} subclass. - * Activities that contain this fragment must implement the - * {@link RenameFavoriteFragmentListener} interface - * to handle interaction events. - * Use the {@link RenameFavoriteFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class RenameFavoriteFragment extends DialogFragment implements DialogInterface.OnClickListener { - public static final String ARG_POS = "POSITION"; - - private EditText nameEditText; - private int position; - private RenameFavoriteFragmentListener mListener; - - public RenameFavoriteFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @return A new instance of fragment TimePickerFragment. - */ - public static RenameFavoriteFragment newInstance(int position) { - RenameFavoriteFragment fragment = new RenameFavoriteFragment(); - Bundle args = new Bundle(); - args.putInt(ARG_POS,position); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - position = getArguments().getInt(ARG_POS); - } - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setView(R.layout.fragment_rename_favorites); - builder.setPositiveButton(getString(android.R.string.ok),this); - builder.setNegativeButton(getString(android.R.string.cancel),this); - builder.setTitle("Rename Favorite"); - - Dialog dialog = builder.create(); - dialog.show(); - nameEditText = dialog.findViewById(R.id.renameFav_name); - return dialog; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof RenameFavoriteFragmentListener) { - mListener = (RenameFavoriteFragmentListener) context; - } else { - throw new RuntimeException(context.toString() + " must implement AddFavoriteFragmentListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_NEGATIVE) { - dialog.dismiss(); - } else { - String name = nameEditText.getText().toString(); - mListener.onRenameSelected(name, position); - } - } - - - /** - * This interface must be implemented by activities that contain this - * fragment to allow an interaction in this fragment to be communicated - * to the activity and potentially other fragments contained in that - * activity. - *

- * See the Android Training lesson Communicating with Other Fragments for more information. - */ - public interface RenameFavoriteFragmentListener { - void onRenameSelected(String newName, int position); - } -} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsActivity.java index 129ecf9a..9ffbebe4 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsActivity.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsActivity.java @@ -1,7 +1,7 @@ package com.a494studios.koreanconjugator.settings; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.view.MenuItem; public class SettingsActivity extends AppCompatActivity { diff --git a/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsFragment.java index a9a6bae9..912ee4dd 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsFragment.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/settings/SettingsFragment.java @@ -6,7 +6,7 @@ import android.preference.PreferenceFragment; import com.a494studios.koreanconjugator.R; -import com.a494studios.koreanconjugator.Utils; +import com.a494studios.koreanconjugator.utils.Utils; /** * A simple {@link PreferenceFragment} subclass. @@ -22,22 +22,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - switch (key) { - case Utils.PREF_LUCKY_KOR: - if (sharedPreferences.getBoolean(key, false)) { - findPreference(key).setSummary(R.string.lucky_kor_true); - } else { - findPreference(key).setSummary(R.string.lucky_kor_false); - } - break; - case Utils.PREF_LUCKY_ENG: - if (sharedPreferences.getBoolean(key, false)) { - findPreference(key).setSummary(R.string.lucky_eng_true); - } else { - findPreference(key).setSummary(R.string.lucky_eng_false); - } - break; - } + // Empty on purpose } @Override diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/BaseActivity.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/BaseActivity.java new file mode 100644 index 00000000..069e85c3 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/BaseActivity.java @@ -0,0 +1,79 @@ +package com.a494studios.koreanconjugator.utils; + +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SearchView; + +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.settings.SettingsActivity; + +import org.rm3l.maoni.Maoni; + +import static com.eggheadgames.aboutbox.activity.AboutActivity.launch; + +public abstract class BaseActivity extends AppCompatActivity { + + private SearchView searchView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.search_menu, menu); + SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); + searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); + searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); + + if(Utils.isAdFree(this) != null && Utils.isAdFree(this)) { + menu.findItem(R.id.overflow_ad_free).setVisible(false); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.overflow_settings){ + startActivity(new Intent(getBaseContext(), SettingsActivity.class)); + return true; + }else if(item.getItemId() == R.id.overflow_about){ + Utils.makeAboutBox(this); + launch(this); + return true; + }else if(item.getItemId() == R.id.overflow_bug) { + Maoni maoni = Utils.makeMaoniActivity(this); + if (maoni != null) { + maoni.start(this); + } + return true; + } else if(item.getItemId() == R.id.overflow_ad_free) { + Logger.getInstance().logViewUpgrade(); + Utils.showAdFreeUpgrade(this); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onResume() { + super.onResume(); + if(searchView != null) { + searchView.setQuery("", false); + searchView.setIconified(true); + searchView.clearFocus(); + } + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/BaseAnimationHandler.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/BaseAnimationHandler.java new file mode 100644 index 00000000..2e3cff35 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/BaseAnimationHandler.java @@ -0,0 +1,24 @@ +package com.a494studios.koreanconjugator.utils; + +import android.content.Context; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +public class BaseAnimationHandler { + protected Context context; + + public BaseAnimationHandler(Context context) { + this.context = context; + } + + public void slideInViews(View extendedBar, View bodyView) { + DecelerateInterpolator interpolator = new DecelerateInterpolator(2); + + extendedBar.setTranslationY(200 * -1); + extendedBar.setVisibility(View.VISIBLE); // Prevents stuttering + extendedBar.animate().setInterpolator(interpolator).translationY(0); + + bodyView.setTranslationY(200); + bodyView.animate().setInterpolator(interpolator).translationY(0); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/ErrorDialogFragment.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/ErrorDialogFragment.java index abba2d1e..951fc879 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/utils/ErrorDialogFragment.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/ErrorDialogFragment.java @@ -4,8 +4,8 @@ import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; /** * A simple {@link DialogFragment} subclass. diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/Logger.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/Logger.java new file mode 100644 index 00000000..c011f8b3 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/Logger.java @@ -0,0 +1,77 @@ +package com.a494studios.koreanconjugator.utils; + +import android.os.Bundle; + +import com.google.firebase.analytics.FirebaseAnalytics; + +public class Logger { + + private static final String EVENT_SELECT_CONJ = "select_conjugation"; + private static final String EVENT_VIEW_UPGRADE = "view_upgrade"; + private static final String EVENT_ADD_FAVORITE = "add_favorite"; + + private FirebaseAnalytics mFirebaseAnalytics; + private static Logger logger; + + + private Logger(FirebaseAnalytics firebaseAnalytics) { + this.mFirebaseAnalytics = firebaseAnalytics; + } + + public static void initialize(FirebaseAnalytics firebaseAnalytics) { + logger = new Logger(firebaseAnalytics); + } + + public static Logger getInstance() { + if(logger == null) { + throw new RuntimeException("Logger not initialized"); + } + return logger; + } + + public void logSelectContent(String term, String pos) { + Bundle bundle = new Bundle(); + bundle.putString(FirebaseAnalytics.Param.ITEM_ID, term); + bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, pos); + mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle); + } + + public void logSelectConjugation(String term, String pos, String conjugation) { + Bundle bundle = new Bundle(); + bundle.putString(FirebaseAnalytics.Param.TERM, term); + bundle.putString("pos", pos); + bundle.putString("conjugation", conjugation); + mFirebaseAnalytics.logEvent(EVENT_SELECT_CONJ, bundle); + } + + public void logViewUpgrade() { + mFirebaseAnalytics.logEvent(EVENT_VIEW_UPGRADE, new Bundle()); + } + + public void logSearch(String term){ + Bundle bundle = new Bundle(); + bundle.putString(FirebaseAnalytics.Param.TERM, term); + bundle.putString("language", detectLanguage(term)); + mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SEARCH, bundle); + } + + public void logFavoriteAdded(String name, String conjugation, boolean honorific) { + Bundle bundle = new Bundle(); + bundle.putString("favorite_name", name); + bundle.putString("conjugation", conjugation); + bundle.putBoolean("is_honorific", honorific); + mFirebaseAnalytics.logEvent(EVENT_ADD_FAVORITE, bundle); + } + + private String detectLanguage(String term){ + boolean isEnglish = true; + for (char c : term.toCharArray()) { + if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z')) { + isEnglish = false; + break; + } + } + + return isEnglish ? "English" : "Korean"; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/RecyclerAnimationHandler.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/RecyclerAnimationHandler.java new file mode 100644 index 00000000..96363761 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/RecyclerAnimationHandler.java @@ -0,0 +1,71 @@ +package com.a494studios.koreanconjugator.utils; + +import android.content.Context; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.a494studios.koreanconjugator.R; + +public class RecyclerAnimationHandler extends BaseAnimationHandler { + + private View extendedBar; + private RecyclerView recyclerView; + + public RecyclerAnimationHandler(View extendedBar, RecyclerView recyclerView, Context context) { + super(context); + this.extendedBar = extendedBar; + this.recyclerView = recyclerView; + } + + public void setupScrollAnimations(LinearLayoutManager layoutManager) { + final boolean[] isAnimating = {false}; + Animation.AnimationListener listener = new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + isAnimating[0] = true; + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimating[0] = false; + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }; + + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if(dx == 0 && dy == 0){ + return; // Bad event + } + + int pos = layoutManager.findFirstCompletelyVisibleItemPosition(); + if(pos == 0) { + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_in); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.VISIBLE); + } else if(!isAnimating[0] && extendedBar.getVisibility() == View.VISIBLE) { + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_out); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.INVISIBLE); + } + } + }); + } + + public void animateListView() { + this.slideInViews(extendedBar,recyclerView); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/ScrollViewAnimationHandler.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/ScrollViewAnimationHandler.java new file mode 100644 index 00000000..406b46cc --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/ScrollViewAnimationHandler.java @@ -0,0 +1,74 @@ +package com.a494studios.koreanconjugator.utils; + +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import com.a494studios.koreanconjugator.R; + +public class ScrollViewAnimationHandler extends BaseAnimationHandler { + private View extendedBar; + private ScrollView scrollView; + private int lastScrollY; + + public ScrollViewAnimationHandler(Context context, View extendedBar, ScrollView scrollView) { + super(context); + this.extendedBar = extendedBar; + this.scrollView = scrollView; + this.lastScrollY = -1; + } + + public void setupScrollAnimation(LinearLayout linearLayout) { + final boolean[] isAnimating = {false}; + Animation.AnimationListener listener = new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + isAnimating[0] = true; + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimating[0] = false; + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }; + + View firstView = linearLayout.getChildAt(0); + scrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { + int scrollY = scrollView.getScrollY(); + if (lastScrollY != -1 && scrollY - lastScrollY != 0) { + + if (isViewVisible(firstView)) { + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_in); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.VISIBLE); + } else if (!isAnimating[0] && extendedBar.getVisibility() == View.VISIBLE) { + Animation anim = AnimationUtils.loadAnimation(context, R.anim.slide_out); + anim.setAnimationListener(listener); + extendedBar.startAnimation(anim); + extendedBar.setVisibility(View.INVISIBLE); + } + } + + lastScrollY = scrollY; + }); + } + + private boolean isViewVisible(View view) { + Rect scrollBounds = new Rect(); + scrollView.getDrawingRect(scrollBounds); + float top = view.getY(); + float bottom = top + view.getHeight(); + + return scrollBounds.top <= top && scrollBounds.bottom >= bottom; + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/SlackHandler.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/SlackHandler.java index 95372e44..33561679 100644 --- a/app/src/main/java/com/a494studios/koreanconjugator/utils/SlackHandler.java +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/SlackHandler.java @@ -4,15 +4,14 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.RadioGroup; import android.widget.Toast; import com.a494studios.koreanconjugator.BuildConfig; import com.a494studios.koreanconjugator.R; -import com.a494studios.koreanconjugator.Utils; -import com.crashlytics.android.Crashlytics; +import com.google.firebase.crashlytics.FirebaseCrashlytics; import org.rm3l.maoni.common.contract.Handler; import org.rm3l.maoni.common.model.Feedback; @@ -56,8 +55,7 @@ public void run() { System.out.println(webApiClient.auth()); success[0] = true; }catch (SlackResponseErrorException e) { - Crashlytics.log("Wrong token code for Slack app?"); - Crashlytics.logException(e); + FirebaseCrashlytics.getInstance().recordException(e); success[0] = false; }catch (SlackException e){ e.printStackTrace(); @@ -137,10 +135,9 @@ public void run() { } if(feedback.screenshotFile != null) { - webApiClient.uploadFile(feedback.screenshotFile, "screenshot", body.toString(), channel); - }else { // User opted-out of sharing a screenshot - webApiClient.postMessage(channel,body.toString()); + webApiClient.uploadFile(feedback.screenshotFile, "screenshot", "", channel); } + webApiClient.postMessage(channel,body.toString()); }catch (SlackException e){ e.printStackTrace(); }finally { diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/Utils.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/Utils.java new file mode 100644 index 00000000..2865b593 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/Utils.java @@ -0,0 +1,236 @@ +package com.a494studios.koreanconjugator.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.a494studios.koreanconjugator.BuildConfig; +import com.a494studios.koreanconjugator.CustomApplication; +import com.a494studios.koreanconjugator.R; +import com.a494studios.koreanconjugator.parsing.Favorite; +import com.a494studios.koreanconjugator.parsing.FavoriteSerializer; +import com.a494studios.koreanconjugator.settings.LegalDisplayActivity; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.SkuDetailsParams; +import com.apollographql.apollo.exception.ApolloNetworkException; +import com.eggheadgames.aboutbox.AboutConfig; +import com.google.firebase.crashlytics.FirebaseCrashlytics; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.mikepenz.aboutlibraries.Libs; +import com.mikepenz.aboutlibraries.LibsBuilder; + +import org.rm3l.maoni.Maoni; + +import java.util.ArrayList; +import java.util.Locale; + +import javax.annotation.Nullable; + +/** + * Created by akash on 1/9/2018. + */ + +public class Utils { + public static final String PREF_FAV_COUNT = "pref_fav_count"; + private static final String PREF_FAV_VALUES = "FAVORITES_VALUES"; + private static final String PREF_FIRST_BOOT = "FIRST_BOOT"; + private static final String PREF_FIRST_TWO = "FIRST_TWO"; + private static final String PREF_AD_FREE = "AD_FREE"; + public static final String SKU_AD_FREE = "ad_free_upgrade"; + + public static boolean isFirstBoot(Context context){ + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_FIRST_BOOT,true); + } + + public static void setFirstBoot(Context context, boolean firstBoot){ + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(PREF_FIRST_BOOT,firstBoot).apply(); + } + + public static boolean isFirstTwo(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_FIRST_TWO, true); + } + + public static void setFirstTwo(Context context, boolean firstTwo) { + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(PREF_FIRST_TWO, firstTwo).apply(); + } + + public static int getFavCount(Context context){ + return PreferenceManager.getDefaultSharedPreferences(context).getInt(PREF_FAV_COUNT, 3); + } + + public static Boolean isAdFree(Context context) { + if (PreferenceManager.getDefaultSharedPreferences(context).contains(PREF_AD_FREE)) { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_AD_FREE, false); + } else { + return null; + } + } + + public static void setAdFree(Context context, boolean adFree) { + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(PREF_AD_FREE, adFree).apply(); + } + + public static void setFavorites(ArrayList data, Context context){ + Gson gson = new Gson(); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putString(PREF_FAV_VALUES,gson.toJson(data)); + editor.putInt(PREF_FAV_COUNT,data.size()); + editor.apply(); + } + + public static ArrayList getFavorites(Context context) { + String jsonString = PreferenceManager.getDefaultSharedPreferences(context).getString(PREF_FAV_VALUES,""); + if(jsonString.isEmpty()){ + return new ArrayList<>(); + } + + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeHierarchyAdapter(Favorite.class, new FavoriteSerializer()); + java.lang.reflect.Type type = new TypeToken>(){}.getType(); + return builder.create().fromJson(jsonString,type); + } + + public static String toTitleCase(String string) { + StringBuilder titleCase = new StringBuilder(); + String[] words = string.split("\\s+"); + for(String word: words) { + String newWord = Character.toTitleCase(word.charAt(0)) + word.substring(1).toLowerCase(); + titleCase.append(newWord); + titleCase.append(" "); + } + + return titleCase.toString().trim(); + } + + public static void makeAboutBox(final Activity activity){ + final AboutConfig aboutConfig = AboutConfig.getInstance(); + aboutConfig.appName = activity.getString(R.string.app_name); + aboutConfig.appIcon = R.mipmap.ic_launcher; + aboutConfig.version = BuildConfig.VERSION_NAME; + aboutConfig.author = "494 Studios"; + aboutConfig.aboutLabelTitle = "About App"; + aboutConfig.packageName = activity.getPackageName(); + aboutConfig.buildType = AboutConfig.BuildType.GOOGLE; + aboutConfig.appPublisher = "494 Studios"; // app publisher for "Try Other Apps" item + aboutConfig.privacyHtmlPath = "file:///android_asset/PrivacyPolicy.html"; + aboutConfig.acknowledgmentHtmlPath = "www.google.com"; + // Custom handler for Acknowledgements and Privacy Policy options + aboutConfig.dialog = (appCompatActivity, url, tag) -> { + if(tag.equals(activity.getString(R.string.egab_privacy_policy))) { + Intent intent = new Intent(activity,LegalDisplayActivity.class); + intent.putExtra("type",LegalDisplayActivity.TYPE_PRIV_POLICY); + activity.startActivity(intent); + }else if(tag.equals(activity.getString(R.string.egab_acknowledgements))){ + new LibsBuilder() + .withExcludedLibraries("support_cardview","support_v4","support_annotations","AppCompat","appcompat_v7","recyclerview_v7","GooglePlayServices","design","volleyplus") + .withLicenseDialog(true) + .withLicenseShown(true) + .withAboutIconShown(false) + .withAboutVersionShownName(false) + .withAboutVersionShown(false) + .withActivityTitle("Libraries Used") + .start(activity); + } + }; + } + + @Nullable + public static Maoni makeMaoniActivity(AppCompatActivity context){ + SlackHandler listener = new SlackHandler(context); + if(!listener.auth()){ + displayErrorDialog(context,"Can't Connect to Server","Check your network settings and try again",null); + return null; + } + return new Maoni.Builder(context, "com.a494studios.koreanconjugator.fileprovider") + .enableScreenCapturingFeature() + .withLogsCapturingFeature(false) + .withHandler(listener) + .withExtraLayout(R.layout.activity_maoni_extra) + .withHeader(R.drawable.feedback_header) + .withTheme(R.style.AppTheme_NoActionBar) + .build(); + } + + public static void showAdFreeUpgrade(Activity activity){ + if(!CustomApplication.isBillingConnected()) { + Toast.makeText(activity, "Couldn't connect to Google Play, please try again later.", Toast.LENGTH_SHORT).show(); + return; + } + + BillingClient billingClient = CustomApplication.getBillingClient(); + ArrayList skuList = new ArrayList<>(); + skuList.add(SKU_AD_FREE); + SkuDetailsParams params = SkuDetailsParams.newBuilder() + .setSkusList(skuList) + .setType(BillingClient.SkuType.INAPP) + .build(); + billingClient.querySkuDetailsAsync(params, (billingResult, skuDetailsList) -> { + if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + for (SkuDetails skuDetails : skuDetailsList) { + if (skuDetails.getSku().equals(SKU_AD_FREE)) { + BillingFlowParams flowParams = BillingFlowParams.newBuilder() + .setSkuDetails(skuDetails) + .build(); + + activity.runOnUiThread(() -> { + BillingResult result = billingClient.launchBillingFlow(activity, flowParams); + if(result.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) { + Toast.makeText(activity,"You've already purchased an ad free upgrade", Toast.LENGTH_SHORT).show(); + } + }); + } + } + } else { + System.out.println("Error getting SKU Details: " + billingResult.getResponseCode()); + } + }); + } + + public static void handleError(Throwable error, AppCompatActivity context, int errorCode, DialogInterface.OnClickListener listener){ + String message = "Error code: " + String.format(Locale.getDefault(), "%03d", errorCode); + ErrorDialogFragment fragment; + if(error instanceof ApolloNetworkException){ + message += ". Check your network settings and try again"; + fragment = ErrorDialogFragment.newInstance("Can't connect to server", message); + } else { + FirebaseCrashlytics.getInstance().recordException(error); + message += ". Try again later or contact support"; + fragment = ErrorDialogFragment.newInstance("Something went wrong", message); + } + + if(listener != null){ + fragment.setListener(listener); + } + context.getSupportFragmentManager() + .beginTransaction() + .add(fragment,"frag_alert") + .commitAllowingStateLoss(); + } + + public static void handleError(Throwable error, AppCompatActivity context, int errorCode) { + handleError(error,context,errorCode,null); + } + + public static void displayErrorDialog(AppCompatActivity context, String title, String msg,DialogInterface.OnClickListener listener){ + ErrorDialogFragment fragment = ErrorDialogFragment.newInstance(title, msg); + if(listener != null){ + fragment.setListener(listener); + } + + context.getSupportFragmentManager() + .beginTransaction() + .add(fragment,"frag_alert") + .commitAllowingStateLoss(); + } +} diff --git a/app/src/main/java/com/a494studios/koreanconjugator/utils/WordInfoView.java b/app/src/main/java/com/a494studios/koreanconjugator/utils/WordInfoView.java new file mode 100644 index 00000000..420ef307 --- /dev/null +++ b/app/src/main/java/com/a494studios/koreanconjugator/utils/WordInfoView.java @@ -0,0 +1,96 @@ +package com.a494studios.koreanconjugator.utils; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.a494studios.koreanconjugator.R; + +import java.util.List; + +public class WordInfoView extends RelativeLayout { + private TextView termView; + private TextView posView; + private LinearLayout defsView; + private boolean showMore = true; + private boolean showAll = false; + private List definitions; + + public WordInfoView(Context context) { + super(context); + init(context); + } + + public WordInfoView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public WordInfoView(Context context, String term, String pos, List definitions, boolean showMore){ + super(context); + init(context); + this.showMore = showMore; + this.definitions = definitions; + + this.setTerm(term); + this.setPos(pos); + this.setDefinitions(definitions); + } + + private void init(Context context){ + View rootView = inflate(context, R.layout.view_word_info, this); + termView = rootView.findViewById(R.id.word_info_term); + posView = rootView.findViewById(R.id.word_info_pos); + defsView = rootView.findViewById(R.id.word_info_recycler); + } + + public void setTerm(String term) { + termView.setText(term); + } + + public void setPos(String pos) { + posView.setText(pos); + } + + public void setShowAll(boolean showAll) { + this.showAll = showAll; + defsView.removeAllViews(); + if(showAll) { + for(int i = 0;i definitions) { + this.definitions = definitions; + defsView.removeAllViews(); + + for(int i = 0;i 2) { + String thirdText = definitions.get(2); + if (showMore) { + thirdText = "+" + (definitions.size() - 2) + " More"; + } + View vi = inflate(getContext(), R.layout.item_word_info, null); + ((TextView) vi.findViewById(R.id.content)).setText(thirdText); + defsView.addView(vi); + } + } + + public boolean getShowAll() { + return showAll; + } +} diff --git a/app/src/main/java/com/github/andkulikov/materialin/MaterialIn.java b/app/src/main/java/com/github/andkulikov/materialin/MaterialIn.java deleted file mode 100644 index a06dadfc..00000000 --- a/app/src/main/java/com/github/andkulikov/materialin/MaterialIn.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.github.andkulikov.materialin; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.res.Resources; -import android.os.Build; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; - -import com.a494studios.koreanconjugator.R; - -/** - * Created by Andrey Kulikov on 07.05.15. - */ -public class MaterialIn { - - public static final String MATERIAL_IN_BLOCK = "materialInBlock"; - public static final String MATERIAL_IN_BLOCK_WITHOUT_SLIDE = "materialInNoSlide"; - - public static void animate(final View view) { - animate(view, Gravity.BOTTOM, Gravity.BOTTOM); - } - - public static void animate(final View view, final int delayDirection, final int slideDirection) { - if (view != null) { - view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - view.getViewTreeObserver().removeOnPreDrawListener(this); - initAnimation(view, 0, 0, convertGravity(view, delayDirection), - convertGravity(view, slideDirection)); - return true; - } - }); - view.invalidate(); - } - } - - private static int convertGravity(View view, int gravity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - boolean isRtl = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - if (gravity == Gravity.START) { - gravity = isRtl ? Gravity.RIGHT : Gravity.LEFT; - } else if (gravity == Gravity.END) { - gravity = isRtl ? Gravity.LEFT : Gravity.RIGHT; - } - } - return gravity; - } - - private static void initAnimation(View view, int offsetX, int offsetY, int delayDir, int slideDir) { - if (offsetX < 0) { - offsetX = 0; - } - if (offsetY < 0) { - offsetY = 0; - } - if (view instanceof ViewGroup && ((ViewGroup) view).getChildCount() > 0 && - !MATERIAL_IN_BLOCK.equals(view.getTag()) && - !MATERIAL_IN_BLOCK_WITHOUT_SLIDE.equals(view.getTag())) { - ViewGroup viewGroup = (ViewGroup) view; - int viewHeight = viewGroup.getHeight(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - int nextOffsetX = offsetX + ((delayDir == Gravity.RIGHT) ? child.getLeft() : - (delayDir == Gravity.LEFT ? viewHeight - child.getRight() : 0)); - int nextOffsetY = offsetY + ((delayDir == Gravity.BOTTOM) ? child.getTop() : - (delayDir == Gravity.TOP ? viewHeight - child.getBottom() : 0)); - initAnimation(child, nextOffsetX, nextOffsetY, delayDir, slideDir); - } - } else { - final Resources res = view.getResources(); - int slideTranslation = res.getDimensionPixelSize(R.dimen.material_in_anim_slide_offset); - if (MATERIAL_IN_BLOCK_WITHOUT_SLIDE.equals(view.getTag())) { - slideTranslation = 0; - } - int multY = 0; - if (slideDir == Gravity.TOP) { - multY = 1; - } else if (slideDir == Gravity.BOTTOM) { - multY = -1; - } - int multX = 0; - if (slideDir == Gravity.LEFT) { - multX = 1; - } else if (slideDir == Gravity.RIGHT) { - multX = -1; - } - int delayOffset = delayDir == Gravity.TOP || delayDir == Gravity.BOTTOM ? offsetY : offsetX; - float delayDenominator = res.getDimension(R.dimen.material_in_delay_denominator); - long delay = (long) (delayOffset / delayDenominator); - startAnimators(view, slideTranslation * multX, slideTranslation * multY, delay); - } - } - - public static void startAnimators(final View view, int startOffsetX, int startOffsetY, long delay) { - if (view.getVisibility() == View.VISIBLE && view.getAlpha() != 0f) { - view.clearAnimation(); - view.animate().cancel(); - final Resources res = view.getResources(); - final float endAlpha = view.getAlpha(); - final float endTranslateX = view.getTranslationX(); - final float endTranslateY = view.getTranslationY(); - view.setAlpha(0); - final Animator fade = ObjectAnimator.ofFloat(view, View.ALPHA, endAlpha); - fade.setDuration(res.getInteger(R.integer.material_in_fade_anim_duration)); - fade.setInterpolator(new AccelerateInterpolator()); - fade.setStartDelay(delay); - fade.start(); - ViewPropertyAnimator slide = view.animate(); - if (startOffsetY != 0) { - view.setTranslationY(startOffsetY); - slide.translationY(endTranslateY); - } else { - view.setTranslationX(startOffsetX); - slide.translationX(endTranslateX); - } - slide.setInterpolator(new DecelerateInterpolator(2)); - slide.setDuration(res.getInteger(R.integer.material_in_slide_anim_duration)); - slide.setStartDelay(delay); - slide.setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - if (fade.isStarted()) { - fade.cancel(); - } - view.setAlpha(endAlpha); - view.setTranslationX(endTranslateX); - view.setTranslationY(endTranslateY); - } - }); - slide.start(); - } - } - -} diff --git a/app/src/main/res/anim/custom_interpolator.xml b/app/src/main/res/anim/custom_interpolator.xml new file mode 100644 index 00000000..d45a73c4 --- /dev/null +++ b/app/src/main/res/anim/custom_interpolator.xml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in.xml b/app/src/main/res/anim/slide_in.xml new file mode 100644 index 00000000..01d0d913 --- /dev/null +++ b/app/src/main/res/anim/slide_in.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out.xml b/app/src/main/res/anim/slide_out.xml new file mode 100644 index 00000000..70ef644d --- /dev/null +++ b/app/src/main/res/anim/slide_out.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background.xml b/app/src/main/res/drawable/background.xml new file mode 100644 index 00000000..9a54a88f --- /dev/null +++ b/app/src/main/res/drawable/background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/honorific_chip.xml b/app/src/main/res/drawable/honorific_chip.xml new file mode 100644 index 00000000..4b7344b5 --- /dev/null +++ b/app/src/main/res/drawable/honorific_chip.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_form.xml b/app/src/main/res/drawable/ic_form.xml new file mode 100644 index 00000000..1d43dcfa --- /dev/null +++ b/app/src/main/res/drawable/ic_form.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_icon.xml b/app/src/main/res/drawable/ic_icon.xml new file mode 100644 index 00000000..c1ec424c --- /dev/null +++ b/app/src/main/res/drawable/ic_icon.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_splash.xml b/app/src/main/res/drawable/ic_splash.xml new file mode 100644 index 00000000..1dea4b18 --- /dev/null +++ b/app/src/main/res/drawable/ic_splash.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_word_cloud.xml b/app/src/main/res/drawable/ic_word_cloud.xml new file mode 100644 index 00000000..25ee1d27 --- /dev/null +++ b/app/src/main/res/drawable/ic_word_cloud.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/search_results_item_bottom.xml b/app/src/main/res/drawable/search_results_item_bottom.xml new file mode 100644 index 00000000..35cd29b9 --- /dev/null +++ b/app/src/main/res/drawable/search_results_item_bottom.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_results_item_middle.xml b/app/src/main/res/drawable/search_results_item_middle.xml new file mode 100644 index 00000000..e497a540 --- /dev/null +++ b/app/src/main/res/drawable/search_results_item_middle.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_results_item_top.xml b/app/src/main/res/drawable/search_results_item_top.xml new file mode 100644 index 00000000..c03f8591 --- /dev/null +++ b/app/src/main/res/drawable/search_results_item_top.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml deleted file mode 100644 index 111be5aa..00000000 --- a/app/src/main/res/layout-land/activity_main.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-land/activity_search.xml b/app/src/main/res/layout-land/activity_search.xml index 089153cc..12dd9268 100644 --- a/app/src/main/res/layout-land/activity_search.xml +++ b/app/src/main/res/layout-land/activity_search.xml @@ -1,10 +1,10 @@ - + tools:context="com.a494studios.koreanconjugator.search.SearchActivity"> - + diff --git a/app/src/main/res/layout/activity_conj_info.xml b/app/src/main/res/layout/activity_conj_info.xml new file mode 100644 index 00000000..75ae1bed --- /dev/null +++ b/app/src/main/res/layout/activity_conj_info.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_conjugation.xml b/app/src/main/res/layout/activity_conjugation.xml new file mode 100644 index 00000000..ea44903a --- /dev/null +++ b/app/src/main/res/layout/activity_conjugation.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_conjugator.xml b/app/src/main/res/layout/activity_conjugator.xml new file mode 100644 index 00000000..2d91964f --- /dev/null +++ b/app/src/main/res/layout/activity_conjugator.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_display.xml b/app/src/main/res/layout/activity_display.xml index 5aca850a..970f7303 100644 --- a/app/src/main/res/layout/activity_display.xml +++ b/app/src/main/res/layout/activity_display.xml @@ -3,157 +3,75 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - xmlns:app="http://schemas.android.com/apk/res-auto" - tools:context="com.a494studios.koreanconjugator.DisplayActivity"> + tools:context="com.a494studios.koreanconjugator.display.DisplayActivity"> + + + + - + - + android:orientation="vertical" + android:paddingBottom="16dp" + android:clipToPadding="false" + android:clipChildren="false"> - - - + android:layout_height="wrap_content" /> - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" /> - - - - - + - + - + - + - - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_favorites.xml b/app/src/main/res/layout/activity_favorites.xml index db1a4b40..2742ee88 100644 --- a/app/src/main/res/layout/activity_favorites.xml +++ b/app/src/main/res/layout/activity_favorites.xml @@ -1,5 +1,6 @@ - + android:layout_height="match_parent" + android:layout_marginTop="@dimen/verticalMargin" + android:divider="@null" /> - + + + diff --git a/app/src/main/res/layout/activity_launch.xml b/app/src/main/res/layout/activity_launch.xml new file mode 100644 index 00000000..6e259dd6 --- /dev/null +++ b/app/src/main/res/layout/activity_launch.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 75658a9d..4ce6fee8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,103 +1,74 @@ - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:paddingEnd="411dp" + android:paddingStart="0dp" + android:orientation="horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintGuide_begin="100dp" + app:layout_constraintStart_toStartOf="parent" /> - + app:layout_constraintTop_toBottomOf="@+id/guideline"> - - + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical" + android:paddingBottom="0dp"> - + - + - + + + --> - + diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index c9f0f41e..d9067158 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -1,10 +1,10 @@ - + tools:context="com.a494studios.koreanconjugator.search.SearchActivity"> - + diff --git a/app/src/main/res/layout/activity_search_results.xml b/app/src/main/res/layout/activity_search_results.xml index fd8a5f6e..735d76fd 100644 --- a/app/src/main/res/layout/activity_search_results.xml +++ b/app/src/main/res/layout/activity_search_results.xml @@ -1,17 +1,28 @@ + android:orientation="vertical" + tools:context="com.a494studios.koreanconjugator.search_results.SearchResultsActivity"> + + - + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingBottom="70dp" + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + app:adUnitId="@string/SEARCH_RESULTS_AD_ID" /> diff --git a/app/src/main/res/layout/dcard_ad.xml b/app/src/main/res/layout/dcard_ad.xml new file mode 100644 index 00000000..3864e598 --- /dev/null +++ b/app/src/main/res/layout/dcard_ad.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dcard_conj_info.xml b/app/src/main/res/layout/dcard_conj_info.xml new file mode 100644 index 00000000..8c6f7448 --- /dev/null +++ b/app/src/main/res/layout/dcard_conj_info.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dcard_list.xml b/app/src/main/res/layout/dcard_list.xml new file mode 100644 index 00000000..ff113205 --- /dev/null +++ b/app/src/main/res/layout/dcard_list.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dcard_search.xml b/app/src/main/res/layout/dcard_search.xml new file mode 100644 index 00000000..35826865 --- /dev/null +++ b/app/src/main/res/layout/dcard_search.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dcard_simpletext.xml b/app/src/main/res/layout/dcard_simpletext.xml new file mode 100644 index 00000000..7ca8edf2 --- /dev/null +++ b/app/src/main/res/layout/dcard_simpletext.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dcard_wod.xml b/app/src/main/res/layout/dcard_wod.xml new file mode 100644 index 00000000..02eee10c --- /dev/null +++ b/app/src/main/res/layout/dcard_wod.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_favorites.xml b/app/src/main/res/layout/fragment_add_favorites.xml index 2c0c8fdf..37e70df8 100644 --- a/app/src/main/res/layout/fragment_add_favorites.xml +++ b/app/src/main/res/layout/fragment_add_favorites.xml @@ -15,27 +15,37 @@ android:layout_height="wrap_content" android:hint="@string/add_fav_hint" android:layout_margin="10dp" - android:textSize="25sp" + android:textSize="22sp" android:imeOptions="actionDone" android:inputType="text"/> + android:layout_marginTop="@dimen/verticalMargin" /> - + android:layout_below="@id/addFav_conjSpinner" + android:layout_marginTop="@dimen/verticalMargin" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_conjugation_card.xml b/app/src/main/res/layout/fragment_conjugation_card.xml deleted file mode 100644 index c12ee96d..00000000 --- a/app/src/main/res/layout/fragment_conjugation_card.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_favorites.xml b/app/src/main/res/layout/fragment_favorites.xml deleted file mode 100644 index db752e95..00000000 --- a/app/src/main/res/layout/fragment_favorites.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rename_favorites.xml b/app/src/main/res/layout/fragment_rename_favorites.xml deleted file mode 100644 index 530746dd..00000000 --- a/app/src/main/res/layout/fragment_rename_favorites.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_conjugation.xml b/app/src/main/res/layout/item_conjugation.xml index 26cb9e5d..281a0dd7 100644 --- a/app/src/main/res/layout/item_conjugation.xml +++ b/app/src/main/res/layout/item_conjugation.xml @@ -1,9 +1,9 @@ - - + android:textSize="24sp" + android:text="갈 거예요" + android:maxLines="1" + app:autoSizeTextType="uniform" + app:autoSizeMaxTextSize="24sp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/item_example.xml b/app/src/main/res/layout/item_example.xml new file mode 100644 index 00000000..13a0c2cb --- /dev/null +++ b/app/src/main/res/layout/item_example.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_result.xml b/app/src/main/res/layout/item_search_result.xml new file mode 100644 index 00000000..9d49a479 --- /dev/null +++ b/app/src/main/res/layout/item_search_result.xml @@ -0,0 +1,22 @@ + + + + + +