diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 00000000..a9d36e9d --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,43 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "commitConvention": "angular", + "contributors": [ + { + "login": "graycreate", + "name": "GRAY", + "avatar_url": "https://avatars.githubusercontent.com/u/5203798?v=4", + "profile": "https://github.com/graycreate", + "contributions": [ + "code" + ] + }, + { + "login": "shiqizhenyes", + "name": "zack", + "avatar_url": "https://avatars.githubusercontent.com/u/10935531?v=4", + "profile": "http://sqz.mobi", + "contributions": [ + "code" + ] + }, + { + "login": "Mystery00", + "name": "Mystery0 M", + "avatar_url": "https://avatars.githubusercontent.com/u/19162205?v=4", + "profile": "https://blog.mystery0.vip", + "contributions": [ + "code" + ] + } + ], + "contributorsPerLine": 7, + "skipCi": true, + "repoType": "github", + "repoHost": "https://github.com", + "projectName": "Android", + "projectOwner": "v2er-app" +} diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..740c2ee9 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,26 @@ +name: Android CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/README.md b/README.md index f8eaabd1..2a0d5ee8 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,41 @@ # V2er-Android + +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) + A beautiful V2EX client built for Android platform. -You could download it from [Google Play](https://play.google.com/store/apps/details?id=me.ghui.v2er) or [CoolApk](https://www.coolapk.com/apk/155428) +You could download it from [Google Play](https://play.google.com/store/apps/details?id=me.ghui.v2er) -![GooglePlay.png](https://s2.loli.net/2021/12/09/zHc68PgFmvMNOZh.png) +![Preview.png](./v2er-preview.png) -# Contribute -TODO +## Contributors + + + + + + + + + + + + +
GRAY
GRAY

💻
zack
zack

💻
Mystery0 M
Mystery0 M

💻
+ + + + + + + + + + + + + +For details please visit [insights](https://github.com/v2er-app/Android/graphs/contributors) # Licensing The source code is licensed under GPL. License is available [here](./LICENSE). diff --git a/app/build.gradle b/app/build.gradle index 972c4cbf..5125aac5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,16 +2,19 @@ apply plugin: 'com.android.application' //apply plugin: "com.flurry.android.symbols" android { - compileSdkVersion 30 + compileSdkVersion 33 defaultConfig { applicationId "me.ghui.v2er" minSdkVersion 27 - targetSdkVersion 30 + targetSdkVersion 33 versionCode rootProject.ext.app.versionCode versionName rootProject.ext.app.versionName testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' multiDexEnabled true vectorDrawables.useSupportLibrary = true + manifestPlaceholders = [ + APPLICATION_ID: applicationId + ] } signingConfigs { release { @@ -58,6 +61,7 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.legacy:legacy-support-v4:1.0.0' androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.google.code.findbugs' @@ -72,6 +76,7 @@ dependencies { implementation 'androidx.palette:palette:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'com.google.android:flexbox:0.2.6' + implementation "com.google.android.material:material:1.4.0" implementation "androidx.annotation:annotation:1.2.0" // 3rd part Dependencies... implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e6a8516a..2822fa6d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,14 +1,17 @@ - + @@ -16,8 +19,8 @@ android:name="android.max_aspect" android:value="2.1" /> + android:theme="@style/SplashTheme" + android:exported="true"> @@ -59,66 +62,52 @@ + android:label="@string/page_daily_hot" /> + android:label="登录V2EX" /> + android:label="两步验证" /> + android:label="Sign in With Google" /> + android:label="主页" /> + android:label="特别关注" /> + android:label="收藏" /> + android:label="节点" /> diff --git a/app/src/main/assets/v2ex.com.cer b/app/src/main/assets/v2ex.com.cer new file mode 100644 index 00000000..e9060ddd --- /dev/null +++ b/app/src/main/assets/v2ex.com.cer @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCjCCBLGgAwIBAgIQDDQDGM1v+PJ15gZd51KyYzAKBggqhkjOPQQDAjBKMQsw +CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX +Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjIwNTAxMDAwMDAwWhcNMjMwNTAx +MjM1OTU5WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG +A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjER +MA8GA1UEAxMIdjJleC5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQa3Xed +Yo0WA1IiCBkOo7jsJFV/iyI34W+WbQ72CqT54tGbN4Dh75Cb3XZVlPF6CVwPOfhN +Guf1DNLe8pMWT5sco4IDWTCCA1UwHwYDVR0jBBgwFoAUpc436uuwdQ6UZ4i0RfrZ +JBCHlh8wHQYDVR0OBBYEFDDEWpUY1uZB2VTEKMdyTWbWOXOXMB8GA1UdEQQYMBaC +CioudjJleC5jb22CCHYyZXguY29tMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAU +BggrBgEFBQcDAQYIKwYBBQUHAwIwewYDVR0fBHQwcjA3oDWgM4YxaHR0cDovL2Ny +bDMuZGlnaWNlcnQuY29tL0Nsb3VkZmxhcmVJbmNFQ0NDQS0zLmNybDA3oDWgM4Yx +aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Nsb3VkZmxhcmVJbmNFQ0NDQS0zLmNy +bDA+BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3 +LmRpZ2ljZXJ0LmNvbS9DUFMwdgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNl +cnRzLmRpZ2ljZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcnQwDAYDVR0T +AQH/BAIwADCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHYArfe++nz/EMiLnT2c +Hj4YarRnKV3PsQwkyoWGNOvcgooAAAGAfVYWOQAABAMARzBFAiEAr8T/GHTEZ5ST +FAd9K5krr4KkwBLRmrVJgTxJrodC1PUCIHYVe+eHWr12bt+FYd6ei3pIpJLIg9oX +/HyWznIKy1l3AHYANc8ZG7+xbFe/D61MbULLu7YnICZR6j/hKu+oA8M71kwAAAGA +fVYWIAAABAMARzBFAiEAu4gv2QeBcejY2pm7uupdyFNDSA+vksdk2WK6FEXcBtkC +IFPdKOugZw3f4dO5wvz2iFqDz/CJ862G/6hPFGaYPbm3AHYAs3N3B+GEUPhjhtYF +qdwRCUp5LbFnDAuH3PADDnk2pZoAAAGAfVYWUgAABAMARzBFAiB0fFp55Dbc4sA3 +LYC0zO6rhlI8q1aFF3p8VDdeeBXCAAIhAI6+C33azX4d+rg18TTMCBIF+bXtiztx +fjO5Lp/uPso0MAoGCCqGSM49BAMCA0cAMEQCIApn7EQolzU9Qeexj0S/XWw7UB5n +C/iL4PezrNnbjmqfAiB6sodlkyGMV8kUtuv9aRVkhSFkge630DbVm1Dhfb2aqg== +-----END CERTIFICATE----- diff --git a/app/src/main/java/me/ghui/v2er/bus/event/DayNightModeEvent.java b/app/src/main/java/me/ghui/v2er/bus/event/DayNightModeEvent.java index ac9a8645..5a194d42 100644 --- a/app/src/main/java/me/ghui/v2er/bus/event/DayNightModeEvent.java +++ b/app/src/main/java/me/ghui/v2er/bus/event/DayNightModeEvent.java @@ -20,7 +20,7 @@ private DayNightModeEvent(@DarkModelUtils.DayNightMode int mode) { this.mode = mode; } - public int getMode() { + public @DarkModelUtils.DayNightMode int getMode() { return mode; } diff --git a/app/src/main/java/me/ghui/v2er/general/App.java b/app/src/main/java/me/ghui/v2er/general/App.java index ea615bdf..e7515e67 100644 --- a/app/src/main/java/me/ghui/v2er/general/App.java +++ b/app/src/main/java/me/ghui/v2er/general/App.java @@ -2,6 +2,8 @@ import android.app.Application; import android.preference.PreferenceManager; +import android.util.Log; + import androidx.annotation.Nullable; import com.flurry.android.FlurryAgent; @@ -13,6 +15,8 @@ import com.tencent.mm.opensdk.openapi.IWXAPI; import com.tencent.mm.opensdk.openapi.WXAPIFactory; +import java.util.Calendar; + import io.reactivex.plugins.RxJavaPlugins; import me.ghui.v2er.BuildConfig; import me.ghui.v2er.R; @@ -32,6 +36,7 @@ public class App extends Application { private static App sInstance; private AppComponent mAppComponent; private IWXAPI mWechat; + public int unReadMsgCount = 0; public static App get() { return sInstance; diff --git a/app/src/main/java/me/ghui/v2er/general/Navigator.java b/app/src/main/java/me/ghui/v2er/general/Navigator.java index 41b8da12..721013fb 100644 --- a/app/src/main/java/me/ghui/v2er/general/Navigator.java +++ b/app/src/main/java/me/ghui/v2er/general/Navigator.java @@ -5,11 +5,17 @@ import android.content.Context; import android.content.Intent; import androidx.core.app.ActivityOptionsCompat; +import androidx.core.util.Pair; + import android.view.View; import java.io.Serializable; import java.lang.ref.WeakReference; import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; /** @@ -75,6 +81,23 @@ public Navigator addFlag(int flag) { return this; } + public Navigator shareElement(View... sourceViews) { + Pair[] sharedElements; + List sourceViewList = new ArrayList<>(sourceViews.length); + Collections.addAll(sourceViewList, sourceViews); + sourceViewList.removeIf(sourceView -> sourceView.getTransitionName() == null || sourceView.getTransitionName().isEmpty()); + sharedElements = new Pair[sourceViewList.size()]; + for (int i = 0; i < sourceViewList.size(); i++) { + sharedElements[i] = new Pair<>(sourceViewList.get(i), + sourceViewList.get(i).getTransitionName()); + } + if (sharedElements.length > 0) { + mOptionsCompat = ActivityOptionsCompat. + makeSceneTransitionAnimation((Activity) mFrom.get(), sharedElements); + } + return this; + } + public Navigator shareElement(View sourceView) { if (sourceView != null && sourceView.getTransitionName() != null) { mOptionsCompat = ActivityOptionsCompat. diff --git a/app/src/main/java/me/ghui/v2er/general/PageHost.java b/app/src/main/java/me/ghui/v2er/general/PageHost.java index dff7fbcf..608e0a3c 100644 --- a/app/src/main/java/me/ghui/v2er/general/PageHost.java +++ b/app/src/main/java/me/ghui/v2er/general/PageHost.java @@ -8,6 +8,7 @@ import me.ghui.v2er.module.settings.ContactFragment; import me.ghui.v2er.module.settings.SettingFragment; import me.ghui.v2er.util.Utils; +import me.ghui.v2er.widget.BaseToolBar; public class PageHost extends BaseActivity { public static final String PAGE_ID = "PageHost.pageId"; @@ -21,7 +22,6 @@ protected int attachLayoutRes() { @Override protected void init() { super.init(); - Utils.setPaddingForStatusBar(mRootView); mPage = (Page) getIntent().getSerializableExtra(PAGE_ID); if (mPage == null) { throw new RuntimeException("wrong page id"); @@ -38,6 +38,14 @@ public void reloadMode(int mode) { .reload(); } + @Override + protected void configToolBar(BaseToolBar toolBar) { + super.configToolBar(toolBar); + if (toolBar != null) { + toolBar.displayHomeAsUpButton(this); + } + } + private Fragment getFragment(Page pageID) { Fragment fragment; String title; diff --git a/app/src/main/java/me/ghui/v2er/helper/BottomNavigationViewHelper.java b/app/src/main/java/me/ghui/v2er/helper/BottomNavigationViewHelper.java new file mode 100644 index 00000000..9f1c4f8b --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/helper/BottomNavigationViewHelper.java @@ -0,0 +1,100 @@ +package me.ghui.v2er.helper; + +import android.content.Context; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.appcompat.view.menu.MenuView; + +import com.flyco.tablayout.widget.MsgView; +import com.google.android.material.badge.BadgeDrawable; +import com.google.android.material.bottomnavigation.BottomNavigationItemView; +import com.google.android.material.bottomnavigation.BottomNavigationMenuView; +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.internal.NavigationMenuView; +import com.google.android.material.navigation.NavigationBarItemView; +import com.google.android.material.navigation.NavigationBarMenuView; +import com.google.android.material.navigationrail.NavigationRailView; + +import me.ghui.v2er.R; +import me.ghui.v2er.util.UnreadMsgUtils; + +public class BottomNavigationViewHelper { + + /** + * 设置图片尺寸 + * @param view + * @param width + * @param height + */ + public static void setImageSize(BottomNavigationView view, int width, int height) { + BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0); + try { + for (int i = 0; i < menuView.getChildCount(); i++) { + BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i); + ImageView imageView = item.findViewById(com.google.android.material.R.id.icon); + imageView.getLayoutParams().width = width; + imageView.getLayoutParams().height = height; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void showBadgeView(Context context, NavigationRailView navigationRailView, int viewIndex, + int showNumber) { + NavigationBarMenuView menuView = (NavigationBarMenuView) navigationRailView.getChildAt(0); + NavigationBarItemView itemView = (NavigationBarItemView) menuView.getChildAt(viewIndex); + ImageView itemIcon = itemView.findViewById(R.id.navigation_bar_item_icon_view); + final View badgeView = LayoutInflater.from(context).inflate(R.layout.message_count_layout, + itemView, false); + MsgView mcMsgView = badgeView.findViewById(R.id.mcMsgView); + final FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(badgeView.getLayoutParams()); + itemIcon.post(() -> { + frameLayoutParams.gravity = Gravity.TOP | Gravity.END; + UnreadMsgUtils.show(context, mcMsgView, showNumber, (width, height) -> { + frameLayoutParams.rightMargin = itemView.getWidth() / 2 - width; //图片的宽度的一半 + frameLayoutParams.topMargin = height / 2; + itemView.addView(badgeView, frameLayoutParams); + }); + }); + } + + + public static void showBadgeView(Context context, BottomNavigationView bottomNavigationView, int viewIndex, + int showNumber) { + BottomNavigationMenuView menuView = (BottomNavigationMenuView) bottomNavigationView.getChildAt(0); + BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(viewIndex); + ImageView itemIcon = itemView.findViewById(R.id.navigation_bar_item_icon_view); + final View badgeView = LayoutInflater.from(context).inflate(R.layout.message_count_layout, + itemView, false); + MsgView mcMsgView = badgeView.findViewById(R.id.mcMsgView); + final FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(badgeView.getLayoutParams()); + itemIcon.post(() -> { + frameLayoutParams.gravity = Gravity.TOP | Gravity.END; + UnreadMsgUtils.show(context, mcMsgView, showNumber, (width, height) -> { + frameLayoutParams.rightMargin = itemView.getWidth() / 2 - width;//图片的宽度的一半 + itemView.addView(badgeView, frameLayoutParams); + }); + }); + } + + public static void hideMsg(Context context, BottomNavigationView bottomNavigationView, int viewIndex) { + BottomNavigationMenuView menuView = (BottomNavigationMenuView) bottomNavigationView.getChildAt(0); + BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(viewIndex); + int childCount = itemView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View childView = itemView.getChildAt(i); + if (childView instanceof FrameLayout) { + itemView.removeView(childView); + break; + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/me/ghui/v2er/injector/component/ExploreComponent.java b/app/src/main/java/me/ghui/v2er/injector/component/ExploreComponent.java new file mode 100644 index 00000000..adc6e13f --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/injector/component/ExploreComponent.java @@ -0,0 +1,16 @@ +package me.ghui.v2er.injector.component; + +import dagger.Component; +import me.ghui.v2er.injector.module.ExploreModule; +import me.ghui.v2er.injector.scope.PerFragment; +import me.ghui.v2er.module.home.ExploreFragment; + +/** + * Created by ghui on 22/05/2017. + */ + +@PerFragment +@Component(dependencies = AppComponent.class, modules = ExploreModule.class) +public interface ExploreComponent { + void inject(ExploreFragment mExploreFragment); +} diff --git a/app/src/main/java/me/ghui/v2er/injector/component/MineComponent.java b/app/src/main/java/me/ghui/v2er/injector/component/MineComponent.java new file mode 100644 index 00000000..b1e5a51b --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/injector/component/MineComponent.java @@ -0,0 +1,12 @@ +package me.ghui.v2er.injector.component; + +import dagger.Component; +import me.ghui.v2er.injector.module.MineModule; +import me.ghui.v2er.injector.scope.PerFragment; +import me.ghui.v2er.module.home.MineFragment; + +@PerFragment +@Component(dependencies = AppComponent.class, modules = MineModule.class) +public interface MineComponent { + void inject(MineFragment fragment); +} diff --git a/app/src/main/java/me/ghui/v2er/injector/component/NodesNavComponent.java b/app/src/main/java/me/ghui/v2er/injector/component/NodesNavComponent.java deleted file mode 100644 index 1dda8c78..00000000 --- a/app/src/main/java/me/ghui/v2er/injector/component/NodesNavComponent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.ghui.v2er.injector.component; - -import dagger.Component; -import me.ghui.v2er.injector.module.NodesNavModule; -import me.ghui.v2er.injector.scope.PerFragment; -import me.ghui.v2er.module.home.NodesNavFragment; - -/** - * Created by ghui on 22/05/2017. - */ - -@PerFragment -@Component(dependencies = AppComponent.class, modules = NodesNavModule.class) -public interface NodesNavComponent { - void inject(NodesNavFragment navFragment); -} diff --git a/app/src/main/java/me/ghui/v2er/injector/module/ExploreModule.java b/app/src/main/java/me/ghui/v2er/injector/module/ExploreModule.java new file mode 100644 index 00000000..5462a3a5 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/injector/module/ExploreModule.java @@ -0,0 +1,163 @@ +package me.ghui.v2er.injector.module; + +import android.content.Context; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import java.io.Serializable; +import java.lang.ref.WeakReference; + +import dagger.Module; +import dagger.Provides; +import me.ghui.v2er.R; +import me.ghui.v2er.adapter.base.CommonAdapter; +import me.ghui.v2er.adapter.base.ItemViewDelegate; +import me.ghui.v2er.adapter.base.MultiItemTypeAdapter; +import me.ghui.v2er.adapter.base.ViewHolder; +import me.ghui.v2er.general.GlideApp; +import me.ghui.v2er.module.home.ExploreContract; +import me.ghui.v2er.module.home.ExploreFragment; +import me.ghui.v2er.module.home.ExplorePresenter; +import me.ghui.v2er.module.node.NodeTopicActivity; +import me.ghui.v2er.module.topic.TopicActivity; +import me.ghui.v2er.module.user.UserHomeActivity; +import me.ghui.v2er.network.bean.DailyHotInfo; +import me.ghui.v2er.network.bean.ExplorePageInfo; +import me.ghui.v2er.network.bean.NewsInfo; +import me.ghui.v2er.network.bean.NodesNavInfo; +import me.ghui.v2er.network.bean.TopicBasicInfo; +import me.ghui.v2er.util.ViewUtils; +import me.ghui.v2er.widget.NavNodesWrapper; + +/** + * Created by ghui on 22/05/2017. + */ + +@Module +public class ExploreModule { + + private ExploreFragment mExploreFragment; + private WeakReference weakContext; + + public ExploreModule(ExploreFragment mExploreFragment) { + this.mExploreFragment = mExploreFragment; + if (mExploreFragment.getContext() != null) { + weakContext = new WeakReference<>(mExploreFragment.getContext()); + } + } + + @Provides + public MultiItemTypeAdapter provideAdapter() { + if (weakContext.get() != null) { + MultiItemTypeAdapter multiItemTypeAdapter = new + MultiItemTypeAdapter(mExploreFragment.getContext()); + + multiItemTypeAdapter.addItemViewDelegate(new ItemViewDelegate(weakContext.get()) { + + @Override + public int getItemViewLayoutId() { + return R.layout.common_list_item; + } + + @Override + public void convert(ViewHolder holder, Serializable serializable, int position) { + super.convert(holder, serializable, position); + DailyHotInfo.Item item = (DailyHotInfo.Item) serializable; + GlideApp.with(mContext) + .load(item.getMember().getAvatar()) + .placeholder(R.drawable.avatar_placeholder_drawable) + .into((ImageView) holder.getView(R.id.avatar_img)); + holder.setText(R.id.user_name_tv, item.getMember().getUserName()); + holder.setText(R.id.time_tv, item.getTime()); + holder.setText(R.id.tagview, item.getNode().getTitle()); + holder.setText(R.id.title_tv, item.getTitle()); + TextView commentTV = holder.getTextView(R.id.comment_num_tv); + commentTV.setText("评论" + item.getReplies()); + ViewUtils.highlightCommentNum(commentTV); + + holder.setOnClickListener(v -> { + DailyHotInfo.Item.Member member = item.getMember(); + UserHomeActivity.open(member.getUserName(), mContext, holder.getImgView(R.id.avatar_img), member.getAvatar()); + }, + R.id.avatar_img, R.id.user_name_tv); + + holder.setOnClickListener(v -> { + View shareView = holder.getView(R.id.avatar_img); + TopicBasicInfo basicInfo = new TopicBasicInfo.Builder(item.getTitle(), item.getMember().getAvatar()) + .author(item.getMember().getUserName()) + .tag(item.getNode().getTitle()) + .tagLink(item.getNode().getUrl().substring(item.getNode().getUrl().indexOf("/go"))) + .commentNum(item.getReplies()) + .build(); + TopicActivity.open(item.getUrl(), + weakContext.get(), shareView, basicInfo); + }, R.id.common_list_item_root_layout); + + holder.setOnClickListener(v -> + NodeTopicActivity.open(item.getNode().getUrl(), mContext), + R.id.tagview); + } + + @Override + public boolean isForViewType(@Nullable Serializable item, int position) { + return item instanceof DailyHotInfo.Item; + } + }); + + multiItemTypeAdapter.addItemViewDelegate(new ItemViewDelegate(weakContext.get()) { + @Override + public int getItemViewLayoutId() { + return R.layout.common_title; + } + + @Override + public void convert(ViewHolder holder, Serializable serializable, int position) { + super.convert(holder, serializable, position); + String title = (String) serializable; + holder.setText(R.id.common_title, title); + } + + @Override + public boolean isForViewType(@Nullable Serializable item, int position) { + return item instanceof String; + } + }); + + multiItemTypeAdapter.addItemViewDelegate(new ItemViewDelegate(weakContext.get()) { + @Override + public int getItemViewLayoutId() { + return R.layout.nodes_nav_item; + } + + @Override + public void convert(ViewHolder holder, Serializable serializable, int position) { + super.convert(holder, serializable, position); + NodesNavInfo.Item item = (NodesNavInfo.Item) serializable; + holder.setText(R.id.node_nav_category_tv, item.getCategory()); + NavNodesWrapper nodesWrapper = holder.getView(R.id.nodes_nav_node_wrapper); + nodesWrapper.setData(item.getNodes()); + } + + @Override + public boolean isForViewType(@Nullable Serializable item, int position) { + return item instanceof NodesNavInfo.Item; + } + + }); + return multiItemTypeAdapter; + } else { + return null; + } + + } + + @Provides + public ExploreContract.IPresenter providePresenter() { + return new ExplorePresenter(mExploreFragment); + } + +} diff --git a/app/src/main/java/me/ghui/v2er/injector/module/MineModule.java b/app/src/main/java/me/ghui/v2er/injector/module/MineModule.java new file mode 100644 index 00000000..3a5764c7 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/injector/module/MineModule.java @@ -0,0 +1,29 @@ +package me.ghui.v2er.injector.module; + +import dagger.Module; +import dagger.Provides; +import me.ghui.v2er.adapter.base.MultiItemTypeAdapter; +import me.ghui.v2er.injector.scope.PerFragment; +import me.ghui.v2er.module.home.MineContract; +import me.ghui.v2er.module.home.MineFragment; +import me.ghui.v2er.module.home.MinePresenter; +import me.ghui.v2er.module.home.MsgContract; +import me.ghui.v2er.module.home.MsgPresenter; +import me.ghui.v2er.network.bean.UserPageInfo; + +@Module +public class MineModule { + + private MineFragment mView; + + public MineModule(MineFragment mView) { + this.mView = mView; + } + + @PerFragment + @Provides + public MineContract.IPresenter providePresenter() { + return new MinePresenter(mView); + } + +} diff --git a/app/src/main/java/me/ghui/v2er/injector/module/NodesNavModule.java b/app/src/main/java/me/ghui/v2er/injector/module/NodesNavModule.java deleted file mode 100644 index 14db1cd9..00000000 --- a/app/src/main/java/me/ghui/v2er/injector/module/NodesNavModule.java +++ /dev/null @@ -1,43 +0,0 @@ -package me.ghui.v2er.injector.module; - -import dagger.Module; -import dagger.Provides; -import me.ghui.v2er.R; -import me.ghui.v2er.adapter.base.CommonAdapter; -import me.ghui.v2er.adapter.base.ViewHolder; -import me.ghui.v2er.module.home.NodesNavConstract; -import me.ghui.v2er.module.home.NodesNavFragment; -import me.ghui.v2er.module.home.NodesNavPresenter; -import me.ghui.v2er.network.bean.NodesNavInfo; -import me.ghui.v2er.widget.NavNodesWrapper; - -/** - * Created by ghui on 22/05/2017. - */ - -@Module -public class NodesNavModule { - private NodesNavFragment mNavFragment; - - public NodesNavModule(NodesNavFragment navFragment) { - mNavFragment = navFragment; - } - - @Provides - public CommonAdapter provideAdapter() { - return new CommonAdapter(mNavFragment.getContext(), R.layout.nodes_nav_item) { - @Override - protected void convert(ViewHolder holder, NodesNavInfo.Item item, int position) { - holder.setText(R.id.node_nav_category_tv, item.getCategory()); - NavNodesWrapper nodesWrapper = holder.getView(R.id.nodes_nav_node_wrapper); - nodesWrapper.setData(item.getNodes()); - } - }; - } - - @Provides - public NodesNavConstract.IPresenter providePresenter() { - return new NodesNavPresenter(mNavFragment); - } - -} diff --git a/app/src/main/java/me/ghui/v2er/module/append/AppendTopicActivity.java b/app/src/main/java/me/ghui/v2er/module/append/AppendTopicActivity.java index d282dace..d881e601 100644 --- a/app/src/main/java/me/ghui/v2er/module/append/AppendTopicActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/append/AppendTopicActivity.java @@ -3,8 +3,11 @@ import android.content.Context; import android.content.Intent; import android.text.TextUtils; +import android.view.MenuItem; import android.widget.EditText; +import androidx.annotation.NonNull; + import butterknife.BindView; import io.reactivex.Observable; import me.ghui.v2er.R; @@ -65,19 +68,22 @@ protected void startInject() { @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - //设置右上角的填充菜单 - toolBar.inflateMenu(R.menu.append_topic_menu); - Utils.setPaddingForStatusBar(toolBar); - Utils.setPaddingForNavbar(mRootView); - toolBar.setOnMenuItemClickListener(item -> { - String content = mContentET.getText().toString(); - if (Check.isEmpty(content)) { - Voast.show("请输入附言内容"); - return false; - } - mPresenter.sendAppend(content); - return true; - }); + } + + @Override + public int attachOptionsMenuRes() { + return R.menu.append_topic_menu; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + String content = mContentET.getText().toString(); + if (Check.isEmpty(content)) { + Voast.show("请输入附言内容"); + return false; + } + mPresenter.sendAppend(content); + return true; } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/base/BaseActivity.java b/app/src/main/java/me/ghui/v2er/module/base/BaseActivity.java index 29b51173..7401422d 100644 --- a/app/src/main/java/me/ghui/v2er/module/base/BaseActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/base/BaseActivity.java @@ -2,16 +2,19 @@ import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import androidx.annotation.CallSuper; import androidx.annotation.ColorInt; import androidx.annotation.LayoutRes; +import androidx.annotation.MenuRes; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.google.android.material.appbar.AppBarLayout; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.appcompat.app.AppCompatActivity; @@ -20,11 +23,15 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Menu; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; +import android.widget.ImageButton; import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toolbar; import com.r0adkll.slidr.model.SlidrInterface; import com.trello.rxlifecycle2.LifecycleTransformer; @@ -32,6 +39,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.lang.reflect.Field; import java.util.Stack; import javax.inject.Inject; @@ -87,7 +95,7 @@ public abstract class BaseActivity extends Rx @Nullable protected SlidrInterface mSlidrInterface; protected DayNightModeEvent mDayNightModeEvent; - + private boolean displayStatusBarArea = true; protected static String KEY(String key) { return Utils.KEY(key); @@ -105,6 +113,16 @@ public void setFirstLoadingDelay(long delay) { @LayoutRes protected abstract int attachLayoutRes(); + + /** + * 显示状态栏区域 + * 在attachToolbar调用,或者之前 + * @param displayStatusBarArea + */ + public void displayStatusBarArea(boolean displayStatusBarArea) { + this.displayStatusBarArea = displayStatusBarArea; + } + /** * Set a default Toolbar, if you don't want certain page to have a toolbar, * just return null; @@ -115,7 +133,14 @@ public void setFirstLoadingDelay(long delay) { protected BaseToolBar attachToolbar() { int layoutId = attachToolBar() == 0 ? R.layout.appbar_wrapper_toolbar : attachToolBar(); mToolbarWrapper = (AppBarLayout) getLayoutInflater().inflate(layoutId, null); - return (BaseToolBar) mToolbarWrapper.findViewById(R.id.inner_toolbar); + BaseToolBar baseToolBar = mToolbarWrapper.findViewById(R.id.inner_toolbar); + if (baseToolBar != null) { + baseToolBar.setTitleTextColor(Theme.getColor(R.attr.icon_tint_color, this)); + baseToolBar.setSubtitleTextColor(Theme.getColor(R.attr.icon_tint_color, this)); + return baseToolBar; + } else { + return null; + } } @LayoutRes @@ -150,8 +175,7 @@ public boolean onToolbarDoubleTaped() { protected void setTitle(String title, String subTitle) { if (mToolbar != null) { - mToolbar.setTitle(title); - mToolbar.setSubtitle(subTitle); + mToolbar.setTitle(title, subTitle); } } @@ -159,6 +183,27 @@ protected void setTitle(String title) { setTitle(title, null); } + public @MenuRes int attachOptionsMenuRes() { + return 0; + } + + /** + * 配置右侧可选菜单 + * @param menu + */ + public void configOptionsMenu(Menu menu) { + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (attachOptionsMenuRes() != 0) { + getMenuInflater().inflate(attachOptionsMenuRes(), menu); + configOptionsMenu(menu); + } + return super.onCreateOptionsMenu(menu); + } + /** * if you want to support ptr, then attach a PtrHandler to it * @@ -343,6 +388,26 @@ protected void autoLoad() { } } + /** + * 查找Appbar + * @param viewGroup + */ + private void findAppbar(ViewGroup viewGroup) { + if (viewGroup instanceof BaseToolBar) { + mToolbar = (BaseToolBar) viewGroup; + } else { + int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + View childView = viewGroup.getChildAt(i); + if (childView instanceof ViewGroup) { + findAppbar((ViewGroup) childView); + } else { + continue; + } + } + } + } + protected ViewGroup onCreateRootView() { if ((mToolbar = attachToolbar()) != null) { LinearLayout rootView = new LinearLayout(this); @@ -377,6 +442,15 @@ protected ViewGroup onCreateRootView() { mRootView.setId(R.id.act_root_view_framelayout); mRootView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mRootView.addView(mContentView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + if (displayStatusBarArea) { + mRootView.setFitsSystemWindows(true); + } else { + mRootView.setFitsSystemWindows(false); + findAppbar(mRootView); + if (mToolbar != null) { + Utils.setPaddingForStatusBar(mToolbar); + } + } mRootView.setBackgroundColor(pageColor()); return mRootView; } diff --git a/app/src/main/java/me/ghui/v2er/module/create/CreateTopicActivity.java b/app/src/main/java/me/ghui/v2er/module/create/CreateTopicActivity.java index f8fe2b37..78c70a8a 100644 --- a/app/src/main/java/me/ghui/v2er/module/create/CreateTopicActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/create/CreateTopicActivity.java @@ -2,6 +2,8 @@ import android.content.Intent; import com.google.android.material.textfield.TextInputLayout; + +import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import android.view.MenuItem; import android.view.View; @@ -36,7 +38,7 @@ */ public class CreateTopicActivity extends BaseActivity implements CreateTopicContract.IView, - Toolbar.OnMenuItemClickListener, NodeSelectFragment.OnSelectedListener { + NodeSelectFragment.OnSelectedListener { @BindView(R.id.create_topic_title_layout) TextInputLayout mTitleTextInputLayout; @BindView(R.id.create_topic_title_et) @@ -102,17 +104,19 @@ protected void autoLoad() { } } + @Override + public int attachOptionsMenuRes() { + return R.menu.post_topic_menu; + } + @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - toolBar.inflateMenu(R.menu.post_topic_menu);//设置右上角的填充菜单 - toolBar.setOnMenuItemClickListener(this); - Utils.setPaddingForStatusBar(toolBar); - Utils.setPaddingForNavbar(mRootView); + toolBar.displayHomeAsUpButton(this); } @Override - public boolean onMenuItemClick(MenuItem menuItem) { + public boolean onOptionsItemSelected(@NonNull MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.action_create_topic: String title = mTitleEt.getText().toString(); diff --git a/app/src/main/java/me/ghui/v2er/module/create/NodeSelectFragment.java b/app/src/main/java/me/ghui/v2er/module/create/NodeSelectFragment.java index 04c49bc4..71fd491b 100644 --- a/app/src/main/java/me/ghui/v2er/module/create/NodeSelectFragment.java +++ b/app/src/main/java/me/ghui/v2er/module/create/NodeSelectFragment.java @@ -3,6 +3,8 @@ import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import androidx.annotation.Nullable; @@ -10,15 +12,19 @@ import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.CheckedTextView; +import android.widget.EditText; import android.widget.Filter; import android.widget.Filterable; +import android.widget.ImageView; import android.widget.Toast; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -31,6 +37,7 @@ import me.ghui.v2er.adapter.base.ViewHolder; import me.ghui.v2er.network.bean.NodesInfo; import me.ghui.v2er.util.ScaleUtils; +import me.ghui.v2er.util.Theme; import me.ghui.v2er.util.Utils; import me.ghui.v2er.widget.BaseRecyclerView; @@ -104,6 +111,31 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { }); SearchView searchView = (SearchView) mToolbar.getMenu().findItem(R.id.action_search_node).getActionView(); searchView.setQueryHint("搜索全部节点"); + EditText searchTextView = searchView + .findViewById(androidx.appcompat.R.id.search_src_text); + ImageView searchButton = searchView + .findViewById(androidx.appcompat.R.id.search_button); + ImageView goButton = searchView + .findViewById(androidx.appcompat.R.id.search_go_btn); + ImageView closeButton = searchView + .findViewById(androidx.appcompat.R.id.search_close_btn); + ImageView voiceButton = searchView + .findViewById(androidx.appcompat.R.id.search_voice_btn); + searchTextView.setTextColor(Theme.getColor(R.attr.icon_tint_color, getContext())); + searchTextView.setHintTextColor(Theme.getColor(R.attr.icon_tint_color, getContext())); + searchButton.getDrawable().setTint(Theme.getColor(R.attr.icon_tint_color, getContext())); + goButton.getDrawable().setTint(Theme.getColor(R.attr.icon_tint_color, getContext())); + closeButton.getDrawable().setTint(Theme.getColor(R.attr.icon_tint_color, getContext())); + voiceButton.getDrawable().setTint(Theme.getColor(R.attr.icon_tint_color, getContext())); + try { + Class searchViewClass = searchView.getClass(); + Field mSearchHintIconField = searchViewClass.getDeclaredField("mSearchHintIcon"); + mSearchHintIconField.setAccessible(true); + Drawable mSearchHintIcon = (Drawable) mSearchHintIconField.get(searchView); + if (mSearchHintIcon != null) { + mSearchHintIcon.setTint(Theme.getColor(R.attr.icon_tint_color, getContext())); + } + }catch (Exception ignore) {} searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { diff --git a/app/src/main/java/me/ghui/v2er/module/drawer/care/SpecialCareActivity.java b/app/src/main/java/me/ghui/v2er/module/drawer/care/SpecialCareActivity.java index 3f1264a3..f9a4db3d 100644 --- a/app/src/main/java/me/ghui/v2er/module/drawer/care/SpecialCareActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/drawer/care/SpecialCareActivity.java @@ -55,7 +55,7 @@ protected void startInject() { @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - Utils.setPaddingForStatusBar(toolBar); + toolBar.displayHomeAsUpButton(this); } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/drawer/dailyhot/DailyHotActivity.java b/app/src/main/java/me/ghui/v2er/module/drawer/dailyhot/DailyHotActivity.java index 7ce08027..f5a67839 100644 --- a/app/src/main/java/me/ghui/v2er/module/drawer/dailyhot/DailyHotActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/drawer/dailyhot/DailyHotActivity.java @@ -62,7 +62,6 @@ protected SwipeRefreshLayout.OnRefreshListener attachOnRefreshListener() { @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - Utils.setPaddingForStatusBar(toolBar); } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/drawer/star/StarActivity.java b/app/src/main/java/me/ghui/v2er/module/drawer/star/StarActivity.java index 8adbb8fd..acca2313 100644 --- a/app/src/main/java/me/ghui/v2er/module/drawer/star/StarActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/drawer/star/StarActivity.java @@ -67,13 +67,12 @@ protected int attachLayoutRes() { protected void configToolBar() { mToolbar.setTitle(getTitle()); - mToolbar.setElevation(0); + mToolbar.displayHomeAsUpButton(this); mToolbar.setOnDoubleTapListener(this); mToolbar.setNavigationOnClickListener(view -> { if (isTaskRoot()) finishToHome(); else onBackPressed(); }); - Utils.setPaddingForStatusBar(mToolbar); } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/gallery/GalleryActivity.java b/app/src/main/java/me/ghui/v2er/module/gallery/GalleryActivity.java index 785e3ed7..40a525e4 100644 --- a/app/src/main/java/me/ghui/v2er/module/gallery/GalleryActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/gallery/GalleryActivity.java @@ -37,12 +37,12 @@ * Created by ghui on 22/10/2017. */ -public class GalleryActivity extends BaseActivity implements SwipeToDismissTouchListener.Callback, Toolbar.OnMenuItemClickListener { +public class GalleryActivity extends BaseActivity implements SwipeToDismissTouchListener.Callback { public static final String EXTRA_IMG_DATA = Utils.KEY("extra_img_data"); @BindView(R.id.gallery_viewpager) ViewPager mViewPager; @BindView(R.id.gallery_toolbar) - Toolbar mToolBar; + BaseToolBar mToolBar; @BindView(R.id.indicator_tv) TextView mIndicatorTv; private ImagesInfo mData; @@ -86,10 +86,7 @@ protected BaseToolBar attachToolbar() { @Override protected void init() { - Utils.setPaddingForStatusBar(mToolBar); - mToolBar.inflateMenu(R.menu.gallery_toolbar_menu); - mToolBar.setOverflowIcon(getDrawable(R.drawable.ic_more_vert_white)); - mToolBar.setOnMenuItemClickListener(this); + mToolBar.displayHomeAsUpButton(this); mToolBar.setNavigationOnClickListener(v -> GalleryActivity.this.onBackPressed()); mData = (ImagesInfo) getIntent().getSerializableExtra(EXTRA_IMG_DATA); @@ -165,7 +162,12 @@ public void onConsume(String path) { } @Override - public boolean onMenuItemClick(MenuItem item) { + public int attachOptionsMenuRes() { + return R.menu.gallery_toolbar_menu; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_open_in_browser: Utils.openInBrowser(getCurrentImage(), this); diff --git a/app/src/main/java/me/ghui/v2er/module/general/WapActivity.java b/app/src/main/java/me/ghui/v2er/module/general/WapActivity.java index adc1c5f9..9443710a 100644 --- a/app/src/main/java/me/ghui/v2er/module/general/WapActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/general/WapActivity.java @@ -4,7 +4,11 @@ import android.content.Intent; import android.graphics.Bitmap; import android.net.http.SslError; + +import androidx.annotation.NonNull; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import android.view.MenuItem; import android.view.ViewGroup; import android.webkit.JsResult; import android.webkit.SslErrorHandler; @@ -88,32 +92,37 @@ protected void configWebView(WebSettings settings) { @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - Utils.setPaddingForStatusBar(toolBar); - mToolbar.inflateMenu(R.menu.wapview_menu); - mToolbar.setOnMenuItemClickListener(menuItem -> { - switch (menuItem.getItemId()) { - case R.id.action_refresh: - showLoading(); - refresh(); - break; - case R.id.action_share: - ShareManager.ShareData shareData = new ShareManager.ShareData.Builder(toolBar.getTitle().toString()) - .link(mCurrentUrl) - .content("链接分享") - .build(); - ShareManager shareManager = new ShareManager(shareData, this); - shareManager.showShareDialog(); - break; - case R.id.action_copy_url: - Utils.copyToClipboard(this, mCurrentUrl); - toast("链接已拷贝成功"); - break; - case R.id.action_open_in_browser: - Utils.openInBrowser(mCurrentUrl, this); - break; - } - return false; - }); + } + + @Override + public int attachOptionsMenuRes() { + return R.menu.wapview_menu; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh: + showLoading(); + refresh(); + break; + case R.id.action_share: + ShareManager.ShareData shareData = new ShareManager.ShareData.Builder(mToolbar.getTitle().toString()) + .link(mCurrentUrl) + .content("链接分享") + .build(); + ShareManager shareManager = new ShareManager(shareData, this); + shareManager.showShareDialog(); + break; + case R.id.action_copy_url: + Utils.copyToClipboard(this, mCurrentUrl); + toast("链接已拷贝成功"); + break; + case R.id.action_open_in_browser: + Utils.openInBrowser(mCurrentUrl, this); + break; + } + return false; } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/home/CheckInContract.java b/app/src/main/java/me/ghui/v2er/module/home/CheckInContract.java index e6cf468a..85cb23bb 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/CheckInContract.java +++ b/app/src/main/java/me/ghui/v2er/module/home/CheckInContract.java @@ -1,6 +1,9 @@ package me.ghui.v2er.module.home; import me.ghui.v2er.module.base.BaseContract; +import me.ghui.v2er.network.GeneralError; +import me.ghui.v2er.network.IGeneralErrorHandler; +import me.ghui.v2er.network.bean.IBase; import me.ghui.v2er.widget.FollowProgressBtn; /** @@ -8,15 +11,30 @@ */ public class CheckInContract { - public interface IView extends BaseContract.IView { - // void onGetCheckInInfo(DailyInfo dailyInfo); - FollowProgressBtn checkInBtn(); + + public abstract static class ICheckInCallBack implements IGeneralErrorHandler { + + void onHasChekIn(String checkInDays) {} + + void onCheckInSuccess(String checkInDays) {} + + void onCheckInFail() {} + + @Override + public void handleError(GeneralError generalError) { + + } + } public interface IPresenter extends BaseContract.IPresenter { - void checkIn(boolean needAutoCheckIn); + + void checkIn(); + + void checkInToDay(); int checkInDays(); + } diff --git a/app/src/main/java/me/ghui/v2er/module/home/CheckInPresenter.java b/app/src/main/java/me/ghui/v2er/module/home/CheckInPresenter.java index 83c2a715..406c8814 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/CheckInPresenter.java +++ b/app/src/main/java/me/ghui/v2er/module/home/CheckInPresenter.java @@ -5,6 +5,7 @@ import me.ghui.v2er.network.APIService; import me.ghui.v2er.network.GeneralConsumer; import me.ghui.v2er.network.bean.DailyInfo; +import me.ghui.v2er.util.RxUtils; import me.ghui.v2er.util.UserUtils; import me.ghui.v2er.util.Utils; @@ -16,35 +17,40 @@ */ public class CheckInPresenter implements CheckInContract.IPresenter { - private CheckInContract.IView mView; + private CheckInContract.ICheckInCallBack callBack; private String checkInDaysStr; + boolean needAutoCheckIn = false; + boolean checkToDay = false; - public CheckInPresenter(CheckInContract.IView view) { - mView = view; + public CheckInPresenter(CheckInContract.ICheckInCallBack callBack) { + this.callBack = callBack; } @Override public void start() { - checkIn(Pref.readBool(R.string.pref_key_auto_checkin)); + needAutoCheckIn = Pref.readBool(R.string.pref_key_auto_checkin); + checkIn(); } @Override - public void checkIn(boolean needAutoCheckIn) { - if (!UserUtils.isLogin()) return; - mView.checkInBtn().startUpdate(); + public void checkIn() { APIService.get().dailyInfo() - .compose(mView.rx(null)) - .subscribe(new GeneralConsumer(mView) { + .compose(RxUtils.io_main()) + .subscribe(new GeneralConsumer(callBack) { @Override public void onConsume(DailyInfo checkInInfo) { if (checkInInfo.hadCheckedIn()) { checkInDaysStr = checkInInfo.getCheckinDays(); - mView.checkInBtn().setStatus(FINISHED, "已签到/" + checkInDaysStr + "天", R.drawable.progress_button_done_icon); + if (callBack != null) { + callBack.onHasChekIn(checkInDaysStr); + } } else { if (needAutoCheckIn) { checkIn(checkInInfo.once()); - } else { - mView.checkInBtn().setStatus(NORMAL, "签到", R.drawable.progress_button_checkin_icon); + } + if (checkToDay) { + checkToDay = false; + checkIn(checkInInfo.once()); } } } @@ -52,32 +58,37 @@ public void onConsume(DailyInfo checkInInfo) { @Override public void onError(Throwable e) { super.onError(e); - mView.checkInBtn().setStatus(NORMAL, "签到", R.drawable.progress_button_checkin_icon); } }); } + @Override + public void checkInToDay() { + checkToDay = true; + checkIn(); + } + @Override public int checkInDays() { return Utils.getIntFromString(checkInDaysStr); } - private void checkIn(String once) { - mView.checkInBtn().startUpdate(); APIService.get() .checkIn(once) - .compose(mView.rx(null)) - .subscribe(new GeneralConsumer(mView) { + .compose(RxUtils.io_main()) + .subscribe(new GeneralConsumer(callBack) { @Override public void onConsume(DailyInfo checkInInfo) { if (checkInInfo.hadCheckedIn()) { checkInDaysStr = checkInInfo.getCheckinDays(); - mView.toast("签到成功/" + checkInDaysStr + "天"); - mView.checkInBtn().setStatus(FINISHED, "已签到/" + checkInDaysStr + "天", R.drawable.progress_button_done_icon); + if (callBack != null) { + callBack.onCheckInSuccess(checkInDaysStr); + } } else { - mView.toast("签到遇到问题!"); - mView.checkInBtn().setStatus(NORMAL, "签到", R.drawable.progress_button_checkin_icon); + if (callBack != null) { + callBack.onCheckInFail(); + } } } }); diff --git a/app/src/main/java/me/ghui/v2er/module/home/NodesNavConstract.java b/app/src/main/java/me/ghui/v2er/module/home/ExploreContract.java similarity index 70% rename from app/src/main/java/me/ghui/v2er/module/home/NodesNavConstract.java rename to app/src/main/java/me/ghui/v2er/module/home/ExploreContract.java index 00b1bd4d..a4b4fbc4 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/NodesNavConstract.java +++ b/app/src/main/java/me/ghui/v2er/module/home/ExploreContract.java @@ -1,16 +1,17 @@ package me.ghui.v2er.module.home; import me.ghui.v2er.module.base.BaseContract; +import me.ghui.v2er.network.bean.ExplorePageInfo; import me.ghui.v2er.network.bean.NodesNavInfo; /** * Created by ghui on 22/05/2017. */ -public class NodesNavConstract { +public class ExploreContract { public interface IView extends BaseContract.IView { - void fillView(NodesNavInfo navInfo); + void fillView(ExplorePageInfo pageInfo); } public interface IPresenter extends BaseContract.IPresenter { diff --git a/app/src/main/java/me/ghui/v2er/module/home/NodesNavFragment.java b/app/src/main/java/me/ghui/v2er/module/home/ExploreFragment.java similarity index 54% rename from app/src/main/java/me/ghui/v2er/module/home/NodesNavFragment.java rename to app/src/main/java/me/ghui/v2er/module/home/ExploreFragment.java index 07e1e7f7..cf88faf5 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/NodesNavFragment.java +++ b/app/src/main/java/me/ghui/v2er/module/home/ExploreFragment.java @@ -5,55 +5,60 @@ import androidx.recyclerview.widget.LinearLayoutManager; import android.view.View; +import java.io.Serializable; + import javax.inject.Inject; import butterknife.BindView; import me.ghui.v2er.R; import me.ghui.v2er.adapter.base.CommonAdapter; -import me.ghui.v2er.injector.component.DaggerNodesNavComponent; -import me.ghui.v2er.injector.module.NodesNavModule; +import me.ghui.v2er.adapter.base.MultiItemTypeAdapter; +import me.ghui.v2er.injector.component.DaggerExploreComponent; +import me.ghui.v2er.injector.module.ExploreModule; +import me.ghui.v2er.network.bean.ExplorePageInfo; +import me.ghui.v2er.network.bean.ExplorePageInfoWrapper; import me.ghui.v2er.network.bean.NodesNavInfo; -import me.ghui.v2er.network.bean.NodesNavInfoWrapper; import me.ghui.v2er.widget.BaseRecyclerView; /** * Created by ghui on 22/03/2017. */ -public class NodesNavFragment extends BaseHomeFragment implements NodesNavConstract.IView { +public class ExploreFragment extends BaseHomeFragment implements ExploreContract.IView { @Inject - CommonAdapter mAdapter; + MultiItemTypeAdapter mAdapter; @BindView(R.id.base_recyclerview) BaseRecyclerView mRecyclerView; - private NodesNavInfoWrapper mNodesNavInfoWrapper; + + private ExplorePageInfoWrapper mExplorePageInfoWrapper; private LinearLayoutManager mLayoutManager; - public static NodesNavFragment newInstance(RestoreData restoreData) { + public static ExploreFragment newInstance(RestoreData restoreData) { Bundle args = new Bundle(); if (restoreData != null) { args.putSerializable(KEY_DATA, restoreData); } - NodesNavFragment fragment = new NodesNavFragment(); + ExploreFragment fragment = new ExploreFragment(); fragment.setArguments(args); return fragment; } - public static NodesNavFragment newInstance() { + public static ExploreFragment newInstance() { return newInstance(null); } - public RestoreData getRestoreData() { + public RestoreData getRestoreData() { int pos = mLayoutManager.findFirstVisibleItemPosition(); int offset = 0; View firstChild = mRecyclerView.getChildAt(0); if (firstChild != null) { offset = firstChild.getTop(); } - if (mNodesNavInfoWrapper == null) { + if (mExplorePageInfoWrapper == null) { return null; } - return new RestoreData<>(1, pos, offset, mNodesNavInfoWrapper); + return new RestoreData<>(1, pos, offset, mExplorePageInfoWrapper); } @Override @@ -63,9 +68,9 @@ protected int attachLayoutRes() { @Override protected void startInject() { - DaggerNodesNavComponent.builder() + DaggerExploreComponent.builder() .appComponent(getAppComponent()) - .nodesNavModule(new NodesNavModule(this)) + .exploreModule(new ExploreModule(this)) .build().inject(this); } @@ -73,10 +78,10 @@ protected void startInject() { protected void init() { mRecyclerView.setLayoutManager(mLayoutManager = new LinearLayoutManager(getContext())); mRecyclerView.setAdapter(mAdapter); - RestoreData restoreData = (RestoreData) getArguments().getSerializable(KEY_DATA); + RestoreData restoreData = (RestoreData) getArguments().getSerializable(KEY_DATA); if (restoreData != null) { - mNodesNavInfoWrapper = restoreData.info; - fillView(restoreData.info.nodesNavInfo); + mExplorePageInfoWrapper = restoreData.info; + fillView(mExplorePageInfoWrapper.explorePageInfo); post(()-> mLayoutManager.scrollToPositionWithOffset(restoreData.scrollPos, restoreData.scrollOffset)); hideLoading(); } @@ -89,14 +94,17 @@ protected SwipeRefreshLayout.OnRefreshListener attachOnRefreshListener() { @Override protected void lazyLoad() { - if (mNodesNavInfoWrapper == null || mNodesNavInfoWrapper.nodesNavInfo == null) { + if (mExplorePageInfoWrapper == null || mExplorePageInfoWrapper.explorePageInfo == null) { super.lazyLoad(); } } @Override - public void fillView(NodesNavInfo navInfo) { - mNodesNavInfoWrapper = NodesNavInfoWrapper.wrapper(navInfo); - mAdapter.setData(navInfo); + public void fillView(ExplorePageInfo pageInfo) { + mExplorePageInfoWrapper = ExplorePageInfoWrapper.wrapper(pageInfo); + pageInfo.setDailyHotInfoTitle(getString(R.string.daily_hot)); + pageInfo.setNodesNavInfoTitle(getString(R.string.node_navigation)); + mAdapter.setData(pageInfo.getItems()); } + } diff --git a/app/src/main/java/me/ghui/v2er/module/home/ExplorePresenter.java b/app/src/main/java/me/ghui/v2er/module/home/ExplorePresenter.java new file mode 100644 index 00000000..e70b29c3 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/module/home/ExplorePresenter.java @@ -0,0 +1,64 @@ +package me.ghui.v2er.module.home; + +import me.ghui.v2er.network.APIService; +import me.ghui.v2er.network.GeneralConsumer; +import me.ghui.v2er.network.bean.DailyHotInfo; +import me.ghui.v2er.network.bean.ExplorePageInfo; +import me.ghui.v2er.network.bean.NodesNavInfo; + +/** + * Created by ghui on 22/05/2017. + */ + +public class ExplorePresenter implements ExploreContract.IPresenter { + + private ExploreContract.IView mView; + private ExplorePageInfo pageInfo; + + public ExplorePresenter(ExploreContract.IView view) { + mView = view; + pageInfo = new ExplorePageInfo(); + } + + /** + * 请求今日热议信息 + */ + private void requestDailyHotInfo() { + APIService.get() + .dailyHot() + .compose(mView.rx()) + .subscribe(new GeneralConsumer(mView) { + @Override + public void onConsume(DailyHotInfo items) { + if (items.isValid()) { + pageInfo.dailyHotInfo = items; + mView.fillView(pageInfo); + } + } + }); + } + + /** + * 请求节点信息 + */ + private void requestNodesNavInfo() { + APIService.get().nodesNavInfo() + .compose(mView.rx()) + .subscribe(new GeneralConsumer(mView) { + @Override + public void onConsume(NodesNavInfo items) { + if (items.isValid()) { + pageInfo.nodesNavInfo = items; + mView.fillView(pageInfo); + } + } + }); + } + + @Override + public void start() { + requestDailyHotInfo(); + requestNodesNavInfo(); + } + +} diff --git a/app/src/main/java/me/ghui/v2er/module/home/MainActivity.java b/app/src/main/java/me/ghui/v2er/module/home/MainActivity.java index 444605c7..ecce5dd0 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/MainActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/home/MainActivity.java @@ -1,18 +1,20 @@ package me.ghui.v2er.module.home; import android.annotation.SuppressLint; -import android.content.Intent; +import android.content.res.Configuration; +import android.util.Log; import android.util.TypedValue; import android.view.Gravity; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.RelativeLayout; import android.widget.TextView; -import androidx.appcompat.widget.SwitchCompat; +import androidx.annotation.NonNull; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -20,84 +22,73 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; -import com.bumptech.glide.request.target.Target; -import com.flyco.tablayout.listener.OnTabSelectListener; import com.flyco.tablayout.widget.MsgView; import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.navigation.NavigationView; +import com.google.android.material.bottomnavigation.BottomNavigationItemView; +import com.google.android.material.bottomnavigation.BottomNavigationMenuView; +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.navigation.NavigationBarView; +import com.google.android.material.navigationrail.NavigationRailView; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.util.Calendar; + import butterknife.BindView; import me.ghui.v2er.R; import me.ghui.v2er.bus.Bus; import me.ghui.v2er.bus.event.TextSizeChangeEvent; import me.ghui.v2er.general.ActivityReloader; -import me.ghui.v2er.general.GlideApp; -import me.ghui.v2er.general.Navigator; -import me.ghui.v2er.general.Page; +import me.ghui.v2er.general.App; +import me.ghui.v2er.helper.BottomNavigationViewHelper; import me.ghui.v2er.module.base.BaseActivity; -import me.ghui.v2er.module.create.CreateTopicActivity; -import me.ghui.v2er.module.drawer.care.SpecialCareActivity; -import me.ghui.v2er.module.drawer.dailyhot.DailyHotActivity; -import me.ghui.v2er.module.drawer.star.StarActivity; -import me.ghui.v2er.module.login.LoginActivity; -import me.ghui.v2er.module.settings.UserManualActivity; -import me.ghui.v2er.module.user.UserHomeActivity; -import me.ghui.v2er.network.bean.UserInfo; -import me.ghui.v2er.util.DarkModelUtils; -import me.ghui.v2er.util.L; +import me.ghui.v2er.network.GeneralError; import me.ghui.v2er.util.ScaleUtils; import me.ghui.v2er.util.Theme; +import me.ghui.v2er.util.UnreadMsgUtils; import me.ghui.v2er.util.UserUtils; import me.ghui.v2er.util.Utils; import me.ghui.v2er.util.ViewUtils; import me.ghui.v2er.widget.BaseToolBar; -import me.ghui.v2er.widget.CSlidingTabLayout; import me.ghui.v2er.widget.FollowProgressBtn; -import me.ghui.v2er.widget.dialog.ConfirmDialog; -import me.ghui.v2er.widget.listener.AppBarStateChangeListener; -public class MainActivity extends BaseActivity implements View.OnClickListener, - UpdateUnReadMsgDelegate, CheckInContract.IView, OnTabSelectListener, +public class MainActivity extends BaseActivity implements View.OnClickListener, UpdateUnReadMsgDelegate, HomeFilterMenu.OnMenuItemClickListener { - private static final String TAB_INDEX = KEY("tab_index"); private static final String PAGE_ONE_DATA = KEY("page_one_data"); private static final String PAGE_TWO_DATA = KEY("page_two_data"); private static final String PAGE_THREE_DATA = KEY("page_three_data"); + private static final String PAGE_FOUR_DATA = KEY("page_four_data"); private static final String TOPIC_IS_APPBAR_EXPANDED = KEY("toolbar_is_appbar_expanded"); public static boolean isAlive; - private final String[] TAB_TITLES = {" 全部", "消息", "节点"}; - @BindView(R.id.left_draw_layout) - DrawerLayout mDrawerLayout; - @BindView(R.id.navigationview_main) - NavigationView mNavigationView; - @BindView(R.id.tablayout_main) - CSlidingTabLayout mSlidingTabLayout; + private final int[] titles = {R.string.feed, R.string.explore, + R.string.message, R.string.mine}; + private final int[] bottomNavigationViewItemIds = {R.id.feed_page, R.id.explore_page, + R.id.message_page, R.id.mine_page}; + @BindView(R.id.main_logo) + ImageView mLogoView; + BottomNavigationView mBottomNavigationView; @BindView(R.id.viewpager_main) ViewPager mViewPager; @BindView(R.id.main_toolbar) BaseToolBar mToolbar; - @BindView(R.id.tab_menu_container) - ViewGroup mTabMenuContainer; + @BindView(R.id.main_container) + ViewGroup mMainContainer; @BindView(R.id.main_appbar) AppBarLayout mAppBarLayout; + DrawerLayout mainDrawerLayout; + NavigationRailView mainNavigationRailView; + private NewsFragment mNewsFragment; private MsgFragment mMsgFragment; - private NodesNavFragment mNavFragment; - private View mNavHeaderView; - private ImageView mAvatarImg; - private TextView mUserNameTv; - private FollowProgressBtn mCheckInBtn; + private ExploreFragment mExploreFragment; + private MineFragment mMineFragment; private CheckInPresenter mCheckInPresenter; - private TextView mTab1View; - private MenuItem mNightMenuItem; - private SwitchCompat mNightSwitch; - private HomeFilterMenu mFilterMenu; - private boolean isAppbarExpanted = true; + private boolean isAppbarExpanded = true; + + @Override protected int attachLayoutRes() { @@ -114,27 +105,32 @@ protected BaseToolBar attachToolbar() { return null; } - @SuppressLint({"CheckResult", "WrongConstant"}) - protected void configToolBar() { - Utils.setPaddingForStatusBar(mAppBarLayout); + @Override + protected void configToolBar(BaseToolBar toolBar) { + super.configToolBar(toolBar); + setSupportActionBar(mToolbar); + } + + @Override + public int attachOptionsMenuRes() { + return R.menu.main_toolbar_menu; + } + + @Override + public void configOptionsMenu(Menu menu) { + super.configOptionsMenu(menu); mToolbar.setOnDoubleTapListener(this); - mToolbar.setElevation(0); - mToolbar.setNavigationIcon(R.drawable.nav); - mToolbar.getNavigationIcon().setTint(Theme.getColor(R.attr.icon_tint_color, this)); - mToolbar.inflateMenu(R.menu.main_toolbar_menu);//设置右上角的填充菜单 - mToolbar.setNavigationOnClickListener(v -> { - if (mDrawerLayout.isDrawerOpen(Gravity.START)) { - mDrawerLayout.closeDrawer(Gravity.START); - } else { - mDrawerLayout.openDrawer(Gravity.START); - } - }); - mToolbar.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.action_search) { - pushFragment(SearchFragment.newInstance()); - } - return true; - }); + mToolbar.setTitle(""); + mLogoView.setVisibility(View.VISIBLE); + mToolbar.setViewTileCenter(mLogoView); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == R.id.action_search) { + pushFragment(SearchFragment.newInstance()); + } + return true; } @Override @@ -149,242 +145,185 @@ public boolean onToolbarDoubleTaped() { return false; } - - private void refreshDayNightItem() { - mNightMenuItem.setTitle(DarkModelUtils.isAutoModeEnabled() ? "深色模式(自动)" : "深色模式"); - mNightSwitch.setChecked(DarkModelUtils.isDarkMode()); + private void changeTitle(int position) { + switch (position) { + case 0: + mToolbar.setTitle(""); + mLogoView.setVisibility(View.VISIBLE); + mToolbar.setViewTileCenter(mLogoView); + break; + case 1: + case 2: + case 3: + mLogoView.setVisibility(View.GONE); + mToolbar.setTitle(titles[position]); + mToolbar.setTileCenter(true); + mToolbar.setTitleTextColor(Theme.getColor(R.attr.icon_tint_color, getContext())); + break; + } } - @Override - protected void init() { - isAlive = true; - configToolBar(); - - mNavigationView.setItemIconTintList(null); - mNavHeaderView = mNavigationView.getHeaderView(0); - mAvatarImg = mNavHeaderView.findViewById(R.id.avatar_img); - mUserNameTv = mNavHeaderView.findViewById(R.id.user_name_tv); - mCheckInBtn = mNavHeaderView.findViewById(R.id.check_in_progress_btn); - mAvatarImg.setOnClickListener(this); - mUserNameTv.setOnClickListener(this); - mCheckInBtn.setOnClickListener(this); - mNightMenuItem = mNavigationView.getMenu().findItem(R.id.day_night_item); - - mAvatarImg.setOnLongClickListener(v -> { - new ConfirmDialog.Builder(getActivity()) - .title("退出登录") - .msg("确定退出吗?") - .positiveText(R.string.ok, dialog -> { - UserUtils.clearLogin(); - Navigator.from(getActivity()) - .setFlag(Intent.FLAG_ACTIVITY_CLEAR_TOP) - .to(MainActivity.class).start(); - }) - .negativeText(R.string.cancel) - .build().show(); - return false; - }); + private void initCheckIn() { + if (UserUtils.isLogin()) { + mCheckInPresenter = new CheckInPresenter(new CheckInContract.ICheckInCallBack() { + + @Override + public void onHasChekIn(String checkInDays) { - mNightSwitch = mNightMenuItem.getActionView().findViewById(R.id.drawer_switch); - updateDrawLayout(); - mNavigationView.setNavigationItemSelectedListener(item -> { + } + + @Override + public void onCheckInSuccess(String checkInDays) { + toast("签到成功/" + checkInDays + "天"); + } + + @Override + public void onCheckInFail() { + toast("签到遇到问题!"); + } + + }); + mCheckInPresenter.start(); + } + } + + private void initBottomNavigationView() { + mBottomNavigationView = findViewById(R.id.main_bottom_navigation_view); + BottomNavigationViewHelper.setImageSize(mBottomNavigationView, + getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_icon_small_size), + getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_icon_small_size)); + mBottomNavigationView.setOnNavigationItemSelectedListener(item -> { switch (item.getItemId()) { - case R.id.hot_nav_item: - Navigator.from(getContext()).to(DailyHotActivity.class).start(); + case R.id.feed_page: + mViewPager.setCurrentItem(0); break; - case R.id.care_nav_item: - Navigator.from(getContext()).to(SpecialCareActivity.class).start(); + case R.id.explore_page: + mViewPager.setCurrentItem(1); break; - case R.id.star_nav_item: - Navigator.from(getContext()).to(StarActivity.class).start(); + case R.id.message_page: + mViewPager.setCurrentItem(2); break; - case R.id.setting_nav_item: - Navigator.from(getContext()).to(Page.SETTING).start(); + case R.id.mine_page: + mViewPager.setCurrentItem(3); break; - case R.id.faq_nav_item: - startActivity(new Intent(getContext(), UserManualActivity.class)); + default: + return false; + } + return true; + }); + } + + private void initLeftNavigationView() { + mainNavigationRailView = findViewById(R.id.mainNavigationRailView); + mainNavigationRailView.setOnItemSelectedListener(item -> { + switch (item.getItemId()) { + case R.id.feed_page: + mViewPager.setCurrentItem(0); break; - case R.id.create_nav_item: - if (UserUtils.notLoginAndProcessToLogin(false, getContext())) return true; - Navigator.from(getContext()).to(CreateTopicActivity.class).start(); + case R.id.explore_page: + mViewPager.setCurrentItem(1); break; - case R.id.day_night_item: - onNightMenuItemClicked(DarkModelUtils.isDarkMode()); + case R.id.message_page: + mViewPager.setCurrentItem(2); break; + case R.id.mine_page: + mViewPager.setCurrentItem(3); + break; + default: + return false; } - delay(50, () -> mDrawerLayout.closeDrawer(Gravity.START, false)); return true; }); + } + private void initNavigationView() { + int screenOrientation = getResources().getConfiguration().orientation; + switch (screenOrientation) { + case Configuration.ORIENTATION_LANDSCAPE: { + initLeftNavigationView(); + break; + } + case Configuration.ORIENTATION_PORTRAIT: { + initBottomNavigationView(); + break; + } + case Configuration.ORIENTATION_SQUARE: + case Configuration.ORIENTATION_UNDEFINED: + break; + } + } - - Menu menu = mNavigationView.getMenu(); - for (int i = 0; i < menu.size(); i++) { - menu.getItem(i).getIcon().setTint(Theme.getColor(R.attr.icon_tint_color, this)); + private void showUnReadMsg() { + int screenOrientation = getResources().getConfiguration().orientation; + if (App.get().unReadMsgCount > 0) { + if (screenOrientation == Configuration.ORIENTATION_PORTRAIT) { + BottomNavigationViewHelper.showBadgeView(this, mBottomNavigationView, + 2, App.get().unReadMsgCount); + } else { + BottomNavigationViewHelper.showBadgeView(this, mainNavigationRailView, + 2, App.get().unReadMsgCount); + } } - mDrawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { + } + + @Override + protected void init() { + isAlive = true; + mViewPager.setAdapter(new SlidePagerAdapter(getSupportFragmentManager())); + mViewPager.setOffscreenPageLimit(3); + mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override - public void onDrawerOpened(View drawerView) { - updateDrawLayout(); + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } - }); - mAppBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { @Override - public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) { - isAppbarExpanted = state == State.EXPANDED; + public void onPageSelected(int position) { + int screenOrientation = getResources().getConfiguration().orientation; + if (screenOrientation == Configuration.ORIENTATION_PORTRAIT) { + mBottomNavigationView.setSelectedItemId(bottomNavigationViewItemIds[position]); + } else { + mainNavigationRailView.setSelectedItemId(bottomNavigationViewItemIds[position]); + } + changeTitle(position); } - }); - TAB_TITLES[0] = TabInfo.getSelectTab().title; - mViewPager.setAdapter(new SlidePagerAdapter(getSupportFragmentManager())); - mViewPager.setOffscreenPageLimit(2); - mSlidingTabLayout.setViewPager(mViewPager, TAB_TITLES); - mSlidingTabLayout.setOnTabSelectListener(this); - configNewsTabTitle(); - initCheckIn(); + @Override + public void onPageScrollStateChanged(int state) { - int index = getIntent().getIntExtra(TAB_INDEX, 0); - mSlidingTabLayout.setCurrentTab(index); - isAppbarExpanted = getIntent().getBooleanExtra(TOPIC_IS_APPBAR_EXPANDED, true); - mAppBarLayout.setExpanded(isAppbarExpanted); + } + }); + initNavigationView(); + showUnReadMsg(); + isAppbarExpanded = getIntent().getBooleanExtra(TOPIC_IS_APPBAR_EXPANDED, true); + initCheckIn(); } @Override protected void reloadMode(int mode) { ActivityReloader.target(this) - .putExtra(TAB_INDEX, mSlidingTabLayout.getCurrentTab()) - .putExtra(TOPIC_IS_APPBAR_EXPANDED, isAppbarExpanted) + .putExtra(TOPIC_IS_APPBAR_EXPANDED, isAppbarExpanded) .putExtra(PAGE_ONE_DATA, mNewsFragment.getRestoreData()) - .putExtra(PAGE_TWO_DATA, mMsgFragment.getRestoreData()) - .putExtra(PAGE_THREE_DATA, mNavFragment.getRestoreData()) + .putExtra(PAGE_TWO_DATA, mExploreFragment.getRestoreData()) + .putExtra(PAGE_THREE_DATA, mMsgFragment.getRestoreData()) .reload(); } - private void onNightMenuItemClicked(boolean isNightMode) { - int wanttedMode = isNightMode ? DarkModelUtils.DEFAULT_MODE : DarkModelUtils.DARK_MODE; - if (DarkModelUtils.isAutoModeEnabled()) { - new ConfirmDialog.Builder(MainActivity.this) - .title("要关闭自动切换模式吗?") - .msg("当前为自动切换模式,确定关闭自动切换吗") - .positiveText("关闭", dialog -> { - DarkModelUtils.saveEnableAutoSwitch(false); - DarkModelUtils.saveModeMannually(wanttedMode); - reloadMode(wanttedMode); - }).negativeText("暂时不用") - .build().show(); - } else { - mNightSwitch.toggle(); - DarkModelUtils.saveModeMannually(wanttedMode); - reloadMode(wanttedMode); - } - } - - private void configNewsTabTitle() { - int padding = ScaleUtils.dp(6f); - mSlidingTabLayout.setTitleViewVerticalPadding(0, padding); - mSlidingTabLayout.setTitleViewVerticalPadding(1, padding); - mSlidingTabLayout.setTitleViewVerticalPadding(2, padding); - mTab1View = mSlidingTabLayout.getTitleView(0); - mTab1View.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.animate_triangle_down, 0); - mTab1View.setCompoundDrawablePadding(ScaleUtils.dp(6)); - } - - private void initCheckIn() { - mCheckInPresenter = new CheckInPresenter(this); - mCheckInPresenter.start(); - } - - private void updateDrawLayout() { - UserInfo userInfo = UserUtils.getUserInfo(); - if (userInfo != null) { - mUserNameTv.setText(userInfo.getUserName()); - GlideApp.with(getContext()) - .load(userInfo.getAvatar()) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .placeholder(R.drawable.avatar_placeholder_drawable) - .into(mAvatarImg); - } else { - mUserNameTv.setText("请先登录"); - mAvatarImg.setImageResource(R.drawable.default_avatar_drawable); - } - refreshDayNightItem(); - } - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.avatar_img: - case R.id.user_name_tv: - if (UserUtils.isLogin()) { - UserHomeActivity.open(UserUtils.getUserInfo().getUserName(), this, null, UserUtils.getUserInfo().getAvatar()); - } else { - Navigator.from(this).to(LoginActivity.class).start(); - } - mDrawerLayout.closeDrawers(); - break; - case R.id.check_in_progress_btn: - if (!UserUtils.isLogin()) { - toast("请先登录!"); - return; - } - if (mCheckInBtn.isNormal()) { - mCheckInPresenter.checkIn(true); - } else if (mCheckInBtn.isFinished()) { - toast("已连续签到" + mCheckInPresenter.checkInDays() + "天"); - } else { - toast("正在签到请稍后..."); - } - break; - } - } - - private int getCurrentTab() { - return mSlidingTabLayout.getCurrentTab(); - } + public void onClick(View v) { } @SuppressLint("WrongConstant") @Override public void onBackPressed() { - if (mDrawerLayout.isDrawerOpen(Gravity.START)) { - mDrawerLayout.closeDrawer(Gravity.START); - return; - } - - if (!isBackableEmpty()) { - super.onBackPressed(); - return; - } - - if (getCurrentTab() != 0) { - mSlidingTabLayout.setCurrentTab(0); - return; - } - - if (mFilterMenu != null && mFilterMenu.isShowing()) { - mFilterMenu.hide(); - return; - } + isBackableEmpty(); super.onBackPressed(); } @Override public void updateUnReadMsg(int position, int count) { - if (count <= 0) {//hide - mSlidingTabLayout.hideMsg(position); - } else { - mSlidingTabLayout.showMsg(position, count); - //config sliding msgview - float padding = getResources().getDimension(R.dimen.mediumTextSize) / 2f; - mSlidingTabLayout.setMsgMargin(1, padding * 0.92f, padding * 0.28f); - MsgView msgView = mSlidingTabLayout.getMsgView(1); - float textSize = getResources().getDimension(R.dimen.tinyTextSize); - msgView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams(); - lp.width = Math.round(textSize * (count + "").length() * 1.5f); - lp.height = Math.round(textSize * 1.5f); - msgView.setLayoutParams(lp); - } + App.get().unReadMsgCount = count; + showUnReadMsg(); } @Override @@ -401,48 +340,20 @@ protected void onDestroy() { } private Fragment getCurrentFragment() { - int pos = getCurrentTab(); + int pos = mViewPager.getCurrentItem(); switch (pos) { case 0: return mNewsFragment; case 1: - return mMsgFragment; + return mExploreFragment; case 2: - return mNavFragment; + return mMsgFragment; + case 3: + return mMineFragment; } return null; } - @Override - public FollowProgressBtn checkInBtn() { - return mCheckInBtn; - } - - @Override - public void onTabSelect(int position) { - L.d("onTabSelect"); - if (position == 0) { - mTab1View.getCompoundDrawables()[2].setTint(Theme.getColor(R.attr.tablayout_selected_color, this)); - } else { - mTab1View.getCompoundDrawables()[2].setTint(Theme.getColor(R.attr.tablayout_unselected_color, this)); - if (mFilterMenu != null && mFilterMenu.isShowing()) { - mFilterMenu.hide(); - } - } - } - - @Override - public void onTabReselect(int position) { - L.d("onTabReSelect"); - if (position == 0) { - if (mFilterMenu == null) { - mFilterMenu = new HomeFilterMenu(mTabMenuContainer, mTab1View); - mFilterMenu.setOnItemClickListner(this); - } - mFilterMenu.toggle(); - } - } - @Override public void onMenuItemClicked(TabInfo tabInfo) { ChangeTabTypeDelegate delegate = mNewsFragment; @@ -454,7 +365,6 @@ public void onTextSizeChanged(TextSizeChangeEvent event) { recreate(); } - public interface ChangeTabTypeDelegate { void changeTabType(TabInfo tabInfo); } @@ -478,14 +388,17 @@ public Fragment getItem(int position) { break; case 1: restoreData = (BaseHomeFragment.RestoreData) getIntent().getSerializableExtra(PAGE_TWO_DATA); - MsgFragment msgFragment = MsgFragment.newInstance(restoreData); - msgFragment.setUpdateUnReadMsgDelegate(MainActivity.this); - fragment = msgFragment; + fragment = ExploreFragment.newInstance(restoreData); break; case 2: restoreData = (BaseHomeFragment.RestoreData) getIntent().getSerializableExtra(PAGE_THREE_DATA); - fragment = NodesNavFragment.newInstance(restoreData); + MsgFragment msgFragment = MsgFragment.newInstance(restoreData); + msgFragment.setUpdateUnReadMsgDelegate(MainActivity.this); + fragment = msgFragment; break; + case 3: + restoreData = (BaseHomeFragment.RestoreData) getIntent().getSerializableExtra(PAGE_FOUR_DATA); + fragment = MineFragment.newInstance(restoreData); } return fragment; } @@ -498,20 +411,21 @@ public Object instantiateItem(ViewGroup container, int position) { mNewsFragment = (NewsFragment) fragment; break; case 1: - mMsgFragment = (MsgFragment) fragment; + mExploreFragment = (ExploreFragment) fragment; break; case 2: - mNavFragment = (NodesNavFragment) fragment; + mMsgFragment = (MsgFragment) fragment; break; + case 3: + mMineFragment = (MineFragment) fragment; } return fragment; } @Override public int getCount() { - return TAB_TITLES.length; + return titles.length; } - } - ; + }; } diff --git a/app/src/main/java/me/ghui/v2er/module/home/MineContract.java b/app/src/main/java/me/ghui/v2er/module/home/MineContract.java new file mode 100644 index 00000000..ed7be974 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/module/home/MineContract.java @@ -0,0 +1,20 @@ +package me.ghui.v2er.module.home; + +import me.ghui.v2er.module.base.BaseContract; +import me.ghui.v2er.network.bean.NotificationInfo; + +/** + * Created by ghui on 10/05/2017. + */ + +public class MineContract { + + public interface IView extends BaseContract.IView { +// void fillView(NotificationInfo info, boolean isLoadMore); + } + + public interface IPresenter extends BaseContract.IPresenter { + + } + +} diff --git a/app/src/main/java/me/ghui/v2er/module/home/MineFragment.java b/app/src/main/java/me/ghui/v2er/module/home/MineFragment.java new file mode 100644 index 00000000..3f758a8b --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/module/home/MineFragment.java @@ -0,0 +1,245 @@ +package me.ghui.v2er.module.home; + +import android.os.Bundle; + +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.bumptech.glide.request.target.Target; + +import javax.inject.Inject; + +import butterknife.BindView; +import de.hdodenhof.circleimageview.CircleImageView; +import me.ghui.v2er.R; +import me.ghui.v2er.adapter.base.MultiItemTypeAdapter; +import me.ghui.v2er.adapter.base.ViewHolder; +import me.ghui.v2er.general.GlideApp; +import me.ghui.v2er.general.Navigator; +import me.ghui.v2er.general.Page; +import me.ghui.v2er.injector.component.DaggerMineComponent; +import me.ghui.v2er.injector.module.MineModule; +import me.ghui.v2er.module.create.CreateTopicActivity; +import me.ghui.v2er.module.drawer.care.SpecialCareActivity; +import me.ghui.v2er.module.drawer.star.StarActivity; +import me.ghui.v2er.module.login.LoginActivity; +import me.ghui.v2er.module.user.UserHomeActivity; +import me.ghui.v2er.network.bean.NewsInfo; +import me.ghui.v2er.network.bean.NotificationInfo; +import me.ghui.v2er.network.bean.UserInfo; +import me.ghui.v2er.util.UserUtils; +import me.ghui.v2er.widget.BaseRecyclerView; +import me.ghui.v2er.widget.FollowProgressBtn; +import me.ghui.v2er.widget.LoadMoreRecyclerView; +import me.ghui.v2er.widget.SectionItemView; + +/** + * 首页我的页面 + */ +public class MineFragment extends BaseHomeFragment implements MineContract.IView, + View.OnClickListener, SectionItemView.OnSectionClickListener { + + @BindView(R.id.mine_root_layout) + ConstraintLayout mRootLayout; + @BindView(R.id.mine_avatar_img) + CircleImageView mAvatarImage; + @BindView(R.id.mine_username_button) + Button mUserNameButton; + @BindView(R.id.mine_user_info_page_button) + Button mUserInfoPageButton; + @BindView(R.id.mine_check_in_progress_btn) + FollowProgressBtn mCheckInButton; + + @BindView(R.id.mine_sec_post) + SectionItemView mSecPost; + @BindView(R.id.mine_sec_themes) + SectionItemView mSecThemes; + @BindView(R.id.mine_sec_bookmark) + SectionItemView mSecBookmark; + @BindView(R.id.mine_sec_focus) + SectionItemView mSecFocus; + @BindView(R.id.mine_sec_settings) + SectionItemView mSecSettings; + + private CheckInPresenter mCheckInPresenter; + + private void initCheckIn() { + if (UserUtils.isLogin()) { + mCheckInButton.setVisibility(View.VISIBLE); + mCheckInPresenter = new CheckInPresenter(new CheckInContract.ICheckInCallBack() { + + @Override + public void onHasChekIn(String checkInDays) { + mCheckInButton.setStatus(FollowProgressBtn.FINISHED, + getString(R.string.number_of_days_checked_in, checkInDays), R.drawable.progress_button_done_icon); + } + + @Override + public void onCheckInSuccess(String checkInDays) { + toast(getString(R.string.number_of_days_check_in_succeed, checkInDays)); + mCheckInButton.setStatus(FollowProgressBtn.FINISHED, + getString(R.string.number_of_days_checked_in, checkInDays), R.drawable.progress_button_done_icon); + } + + @Override + public void onCheckInFail() { + toast(getString(R.string.problems_with_check_in)); + mCheckInButton.setStatus(FollowProgressBtn.NORMAL, getString(R.string.check_in), + R.drawable.progress_button_checkin_icon); + } + + }); + mCheckInPresenter.start(); + } + } + + public static MineFragment newInstance(BaseHomeFragment.RestoreData restoreData) { + Bundle args = new Bundle(); + if (restoreData != null) { + args.putSerializable(KEY_DATA, restoreData); + } + MineFragment fragment = new MineFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + protected int attachLayoutRes() { + return R.layout.fragment_mine; + } + + @Override + protected void startInject() { + DaggerMineComponent.builder() + .appComponent(getAppComponent()) + .mineModule(new MineModule(this)) + .build() + .inject(this); + } + + private UserInfo userInfo; + private void initDisplayUserName() { + userInfo = UserUtils.getUserInfo(); + if (userInfo == null) { + mUserNameButton.setContentDescription(getText(R.string.please_login_first)); + mUserNameButton.setText(R.string.please_login_first); + mAvatarImage.setImageResource(R.drawable.default_avatar_drawable); + } else { + mUserNameButton.setContentDescription(userInfo.getUserName()); + mUserNameButton.setText(userInfo.getUserName()); + if (getContext() != null) { + GlideApp.with(getContext()) + .load(userInfo.getAvatar()) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .placeholder(R.drawable.avatar_placeholder_drawable) + .into(mAvatarImage); + } + } + } + + @Override + protected void init() { + hideLoading(); + mAvatarImage.setOnClickListener(this); + mUserNameButton.setOnClickListener(this); + mUserInfoPageButton.setOnClickListener(this); + mCheckInButton.setOnClickListener(this); + mSecPost.setOnSectionClickListener(this); + mSecThemes.setOnSectionClickListener(this); + mSecBookmark.setOnSectionClickListener(this); + mSecFocus.setOnSectionClickListener(this); + mSecSettings.setOnSectionClickListener(this); + initDisplayUserName(); + initCheckIn(); + } + + private void goToUserInfoPage() { + if (UserUtils.isLogin()) { + if (getContext() != null) { + UserHomeActivity.open(userInfo.getUserName(), getContext(), + userInfo.getAvatar(), mAvatarImage, mUserNameButton); + } + } else { + if (getContext() != null) { + Navigator.from(getContext()).to(LoginActivity.class).start(); + } + } + } + + /** + * 发帖 + */ + private void goToPost() { + if (UserUtils.notLoginAndProcessToLogin(false, getContext())) { + return; + } + if (UserUtils.isLogin()) { + Navigator.from(getContext()).to(CreateTopicActivity.class).start(); + } + } + + /** + * 收藏 + */ + private void goToBookmark() { + if (UserUtils.notLoginAndProcessToLogin(false, getContext())) { + return; + } + if (UserUtils.isLogin()) { + Navigator.from(getContext()).to(StarActivity.class).start(); + } + } + + /** + * 关注 + */ + private void goToFocus() { + if (UserUtils.notLoginAndProcessToLogin(false, getContext())) { + return; + } + if (UserUtils.isLogin()) { + Navigator.from(getContext()).to(SpecialCareActivity.class).start(); + } + } + + /** + * 设置 + */ + private void goToSetting() { + Navigator.from(getContext()).to(Page.SETTING).start(); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.mine_check_in_progress_btn: + mCheckInButton.startUpdate(); + mCheckInPresenter.checkInToDay(); + break; + case R.id.mine_avatar_img: + case R.id.mine_username_button: + case R.id.mine_user_info_page_button: + goToUserInfoPage(); + break; + case R.id.mine_sec_post: + goToPost(); + break; + case R.id.mine_sec_bookmark: + goToBookmark(); + break; + case R.id.mine_sec_focus: + goToFocus(); + break; + case R.id.mine_sec_settings: + goToSetting(); + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ghui/v2er/module/home/MinePresenter.java b/app/src/main/java/me/ghui/v2er/module/home/MinePresenter.java new file mode 100644 index 00000000..caab1ae7 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/module/home/MinePresenter.java @@ -0,0 +1,16 @@ +package me.ghui.v2er.module.home; + +public class MinePresenter implements MineContract.IPresenter { + + private MineContract.IView mView; + + public MinePresenter(MineContract.IView mView) { + this.mView = mView; + } + + @Override + public void start() { + + } + +} diff --git a/app/src/main/java/me/ghui/v2er/module/home/NewsFragment.java b/app/src/main/java/me/ghui/v2er/module/home/NewsFragment.java index f417ef28..81be2837 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/NewsFragment.java +++ b/app/src/main/java/me/ghui/v2er/module/home/NewsFragment.java @@ -124,6 +124,19 @@ protected void init() { protected void lazyLoad() { if (mNewsInfo == null) { super.lazyLoad(); + } else { + RestoreData restoreData = getRestoreData(); + if (restoreData != null) { + mNewsInfo = restoreData.info; + mRecyclerView.setWillLoadPage(restoreData.page); + if (mNewsInfo.getItems() != null) { + fillView(mNewsInfo, false); + post(() -> mLayoutManager.scrollToPositionWithOffset(restoreData.scrollPos, restoreData.scrollOffset)); + hideLoading(); + } else { + mPresenter.start(); + } + } } } diff --git a/app/src/main/java/me/ghui/v2er/module/home/NodesNavPresenter.java b/app/src/main/java/me/ghui/v2er/module/home/NodesNavPresenter.java deleted file mode 100644 index 1c7e7324..00000000 --- a/app/src/main/java/me/ghui/v2er/module/home/NodesNavPresenter.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.ghui.v2er.module.home; - -import me.ghui.v2er.network.APIService; -import me.ghui.v2er.network.GeneralConsumer; -import me.ghui.v2er.network.bean.NodesNavInfo; - -/** - * Created by ghui on 22/05/2017. - */ - -public class NodesNavPresenter implements NodesNavConstract.IPresenter { - - private NodesNavConstract.IView mView; - - public NodesNavPresenter(NodesNavConstract.IView view) { - mView = view; - } - - @Override - public void start() { - APIService.get().nodesNavInfo() - .compose(mView.rx()) - .subscribe(new GeneralConsumer(mView) { - @Override - public void onConsume(NodesNavInfo items) { - mView.fillView(items); - } - }); - } - -} diff --git a/app/src/main/java/me/ghui/v2er/module/home/SearchFragment.java b/app/src/main/java/me/ghui/v2er/module/home/SearchFragment.java index 9713b435..75be42cf 100644 --- a/app/src/main/java/me/ghui/v2er/module/home/SearchFragment.java +++ b/app/src/main/java/me/ghui/v2er/module/home/SearchFragment.java @@ -86,7 +86,6 @@ protected boolean showLoadingOnCreateView() { @Override protected void init() { - Utils.setPaddingForStatusBar(mSearchRootView); mCardView.setCardBackgroundColor(Theme.getColor(R.attr.dialog_bg_color, getContext())); mResultRecyV.addDivider(DarkModelUtils.isDarkMode() ? 0XFF000000 : 0XFFF5F5F5, 6); mResultRecyV.setLayoutManager(new LinearLayoutManager(getContext())); diff --git a/app/src/main/java/me/ghui/v2er/module/login/LoginActivity.java b/app/src/main/java/me/ghui/v2er/module/login/LoginActivity.java index 8ca8a0f0..75c61faa 100644 --- a/app/src/main/java/me/ghui/v2er/module/login/LoginActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/login/LoginActivity.java @@ -3,10 +3,12 @@ import android.content.Intent; import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.textfield.TextInputLayout; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -77,7 +79,6 @@ protected void startInject() { @Override protected void init() { super.init(); - Utils.setPaddingForNavbar(mRootView); } @Override @@ -88,21 +89,25 @@ protected void reloadMode(int mode) { @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - Utils.setPaddingForStatusBar(toolBar); - toolBar.setElevation(0); - toolBar.inflateMenu(R.menu.login_toolbar_menu); - toolBar.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.action_register) { - Utils.openInBrowser(Constants.BASE_URL + "/signup?r=ghui", this); - } else if (item.getItemId() == R.id.action_forgot_psw) { - Utils.openInBrowser(Constants.BASE_URL + "/forgot", this); - } else if (item.getItemId() == R.id.action_faq) { - Navigator.from(this).to(UserManualActivity.class).start(); - } else if (item.getItemId() == R.id.action_about) { - Utils.openInBrowser(Constants.BASE_URL + "/about", this); - } - return true; - }); + } + + @Override + public int attachOptionsMenuRes() { + return R.menu.login_toolbar_menu; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == R.id.action_register) { + Utils.openInBrowser(Constants.BASE_URL + "/signup?r=ghui", this); + } else if (item.getItemId() == R.id.action_forgot_psw) { + Utils.openInBrowser(Constants.BASE_URL + "/forgot", this); + } else if (item.getItemId() == R.id.action_faq) { + Navigator.from(this).to(UserManualActivity.class).start(); + } else if (item.getItemId() == R.id.action_about) { + Utils.openInBrowser(Constants.BASE_URL + "/about", this); + } + return true; } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/login/SignInWithGoogleActivity.java b/app/src/main/java/me/ghui/v2er/module/login/SignInWithGoogleActivity.java index 14c85e8e..79aa9cef 100644 --- a/app/src/main/java/me/ghui/v2er/module/login/SignInWithGoogleActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/login/SignInWithGoogleActivity.java @@ -40,7 +40,7 @@ public static void open(Context context, String once) { @Override protected void configWebView(WebSettings settings) { super.configWebView(settings); - settings.setUserAgentString(APIService.WAP_USER_AGENT); + settings.setUserAgentString(APIService.WAP_Android_USER_AGENT); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setDatabaseEnabled(true); @@ -63,7 +63,7 @@ public void onBackPressed() { protected boolean checkIntercept(String currentUrl) { L.d("url: " + currentUrl); if (currentUrl.startsWith(Constants.BASE_URL + "/mission/daily")) { - mWebView.getSettings().setUserAgentString(APIService.WAP_USER_AGENT); + mWebView.getSettings().setUserAgentString(APIService.WAP_Android_USER_AGENT); doGetUserInfo(); return true; } diff --git a/app/src/main/java/me/ghui/v2er/module/node/NodeTopicActivity.java b/app/src/main/java/me/ghui/v2er/module/node/NodeTopicActivity.java index 88e2de94..33b88de5 100644 --- a/app/src/main/java/me/ghui/v2er/module/node/NodeTopicActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/node/NodeTopicActivity.java @@ -5,10 +5,14 @@ import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import androidx.recyclerview.widget.LinearLayoutManager; + +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; @@ -162,6 +166,7 @@ protected int attachLayoutRes() { @Override protected BaseToolBar attachToolbar() { + displayStatusBarArea(false); return null; } @@ -186,42 +191,20 @@ protected boolean supportShareElement() { } @Override - protected void init() { - getWindow().setStatusBarColor(Color.TRANSPARENT); - Utils.setPaddingForStatusBar(mToolbar); - setEnterSharedElementCallback(mCallback); - mToolbar.setOnDoubleTapListener(this); - mToolbar.inflateMenu(R.menu.node_info_toolbar_menu); - mLoveMenuItem = mToolbar.getMenu().findItem(R.id.action_star); - mToolbar.setNavigationOnClickListener(view -> onBackPressed()); - mToolbar.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.action_star) { - onStarBtnClicked(); - } else if (item.getItemId() == R.id.action_share) { - if (mNodeInfo == null) return false; - String desc = mNodeInfo.getHeader(); - String title = mNodeInfo.getTitle(); -// ShareManager.shareText(title, mNodeInfo.getUrl(), this); - ShareManager.ShareData shareData = new ShareManager.ShareData.Builder(title) - .content(Vtml.fromHtml(desc).toString()) - .link(mNodeInfo.getUrl()) - .img(mNodeInfo.getAvatar()) - .build(); - ShareManager shareManager = new ShareManager(shareData, this); - shareManager.showShareDialog(); - } else if (item.getItemId() == R.id.action_block) { + protected void configToolBar(BaseToolBar toolBar) { + super.configToolBar(toolBar); + } - } - return true; - }); - mRecyclerView.setAppBarTracking(this); - mRecyclerView.setOnLoadMoreListener(this); -// mRecyclerView.addDivider(); - mLayoutManager = new LinearLayoutManager(this); - mRecyclerView.setLayoutManager(mLayoutManager); - mRecyclerView.setAdapter(mAdapter); - mAdapter.setOnItemClickListener(this); + @Override + public int attachOptionsMenuRes() { + return R.menu.node_info_toolbar_menu; + } + + @Override + public void configOptionsMenu(Menu menu) { + super.configOptionsMenu(menu); + mLoveMenuItem = menu.findItem(R.id.action_star); mAppBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { @Override public void onStateChanged(AppBarLayout appBarLayout, State state) { @@ -251,9 +234,43 @@ public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { mAppBarIdle = (mAppBarOffset >= 0) || (mAppBarOffset <= mAppBarMaxOffset); } }); + } - mAppBarLayout.post(() -> mAppBarMaxOffset = -mAppBarLayout.getTotalScrollRange()); + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == R.id.action_star) { + onStarBtnClicked(); + } else if (item.getItemId() == R.id.action_share) { + if (mNodeInfo == null) return false; + String desc = mNodeInfo.getHeader(); + String title = mNodeInfo.getTitle(); +// ShareManager.shareText(title, mNodeInfo.getUrl(), this); + ShareManager.ShareData shareData = new ShareManager.ShareData.Builder(title) + .content(Vtml.fromHtml(desc).toString()) + .link(mNodeInfo.getUrl()) + .img(mNodeInfo.getAvatar()) + .build(); + ShareManager shareManager = new ShareManager(shareData, this); + shareManager.showShareDialog(); + } else if (item.getItemId() == R.id.action_block) { + + } + return true; + } + @Override + protected void init() { + getWindow().setStatusBarColor(Color.TRANSPARENT); + setEnterSharedElementCallback(mCallback); + mToolbar.displayHomeAsUpButton(this); + mToolbar.setOnDoubleTapListener(this); + mRecyclerView.setAppBarTracking(this); + mRecyclerView.setOnLoadMoreListener(this); + mLayoutManager = new LinearLayoutManager(this); + mRecyclerView.setLayoutManager(mLayoutManager); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + mAppBarLayout.post(() -> mAppBarMaxOffset = -mAppBarLayout.getTotalScrollRange()); if (mNodeInfo != null) { fillHeaderView(mNodeInfo); } @@ -422,7 +439,7 @@ public void afterUnIgnoreNode() { private void toggleStar(boolean isStared) { mLoveMenuItem.setIcon(isStared ? - R.drawable.ic_star_selected : R.drawable.ic_star_normal); + R.drawable.ic_bookmarked : R.drawable.ic_bookmark); mLoveMenuItem.getIcon().setTint(Theme.getColor(R.attr.icon_tint_color, this)); if (isStared) { mStarBtn.setStatus(FollowProgressBtn.FINISHED, "已收藏", R.drawable.progress_button_done_icon); diff --git a/app/src/main/java/me/ghui/v2er/module/settings/ContactFragment.java b/app/src/main/java/me/ghui/v2er/module/settings/ContactFragment.java index 6c268f98..b5adc819 100644 --- a/app/src/main/java/me/ghui/v2er/module/settings/ContactFragment.java +++ b/app/src/main/java/me/ghui/v2er/module/settings/ContactFragment.java @@ -44,7 +44,6 @@ public void onActivityCreated(Bundle savedInstanceState) { ListView list = rootView.findViewById(android.R.id.list); if (list != null) { // list.setDivider(getActivity().getDrawable(R.drawable.common_divider)); - Utils.setPaddingForNavbar(list); } } diff --git a/app/src/main/java/me/ghui/v2er/module/settings/SettingFragment.java b/app/src/main/java/me/ghui/v2er/module/settings/SettingFragment.java index ec0942bc..53b41878 100644 --- a/app/src/main/java/me/ghui/v2er/module/settings/SettingFragment.java +++ b/app/src/main/java/me/ghui/v2er/module/settings/SettingFragment.java @@ -56,6 +56,7 @@ public void onCreate(Bundle savedInstanceState) { loginPreference = findPreference(getString(R.string.pref_key_value_toggle_log)); loginPreference.setOnPreferenceClickListener(this); loginPreference.setTitle(UserUtils.isLogin() ? R.string.logout_str : R.string.login_str); + findPreference(getString(R.string.pref_key_help_and_feedback)).setOnPreferenceClickListener(this); findPreference(getString(R.string.pref_key_auto_checkin)).setOnPreferenceClickListener(this); findPreference(getString(R.string.pref_key_highlight_topic_owner_reply_item)).setOnPreferenceClickListener(this); findPreference(getString(R.string.pref_key_is_scan_in_reverse)).setOnPreferenceClickListener(this::onPreferenceClick); @@ -85,8 +86,6 @@ public void onActivityCreated(Bundle savedInstanceState) { ListView list = rootView.findViewById(android.R.id.list); if (list != null) { list.setDivider(null); -// list.setDivider(getActivity().getDrawable(R.drawable.common_divider)); - Utils.setPaddingForNavbar(list); } } @@ -102,6 +101,9 @@ public boolean onPreferenceClick(Preference preference) { Voast.show("成功清理" + size + "缓存"); } return true; + } else if (key.equals(getString(R.string.pref_key_help_and_feedback))) { + startActivity(new Intent(getContext(), UserManualActivity.class)); + return true; } else if (key.equals(getString(R.string.pref_key_check_update))) { Utils.openStorePage(); return true; diff --git a/app/src/main/java/me/ghui/v2er/module/settings/UserManualActivity.java b/app/src/main/java/me/ghui/v2er/module/settings/UserManualActivity.java index 304c9e85..d4015e36 100644 --- a/app/src/main/java/me/ghui/v2er/module/settings/UserManualActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/settings/UserManualActivity.java @@ -22,8 +22,6 @@ protected int attachLayoutRes() { @Override protected void configToolBar(BaseToolBar toolBar) { super.configToolBar(toolBar); - Utils.setPaddingForStatusBar(toolBar); - Utils.setPaddingForNavbar(mHtmlView); } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/topic/HtmlView.java b/app/src/main/java/me/ghui/v2er/module/topic/HtmlView.java index bbbfeebd..f90990df 100644 --- a/app/src/main/java/me/ghui/v2er/module/topic/HtmlView.java +++ b/app/src/main/java/me/ghui/v2er/module/topic/HtmlView.java @@ -3,7 +3,13 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.AttributeSet; import android.view.ViewGroup; import android.webkit.JavascriptInterface; @@ -29,6 +35,7 @@ import java.util.List; import io.reactivex.Observable; +import me.ghui.v2er.util.AdvertisementFilterUtil; import me.ghui.v2er.util.Assets; import me.ghui.v2er.util.Check; import me.ghui.v2er.BuildConfig; @@ -53,19 +60,19 @@ public class HtmlView extends WebView { public HtmlView(Context context) { super(context); - init(); + init(context); } public HtmlView(Context context, AttributeSet attrs) { super(context, attrs); - init(); + init(context); } public void setOnHtmlRenderListener(OnHtmlRenderListener onHtmlRenderListener) { this.onHtmlRenderListener = onHtmlRenderListener; } - private void init() { + private void init(Context context) { WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG); WebSettings settings = getSettings(); settings.setJavaScriptEnabled(true); @@ -79,7 +86,7 @@ private void init() { settings.setTextZoom(100); settings.setAllowUniversalAccessFromFileURLs(true); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - setWebViewClient(new V2exWebViewClient()); + setWebViewClient(new V2exWebViewClient(context)); setVerticalScrollBarEnabled(false); addJavascriptInterface(new ImgClickJSInterface(), "imagelistener"); setBackgroundColor(DarkModelUtils.isDarkMode() ? @@ -130,8 +137,38 @@ public interface OnHtmlRenderListener { void onRenderCompleted(); } + private Message v2exWebViewClientMsg; + private V2exWebViewClientHandler v2exWebViewClientHandler; + private int advertisementFilterMsg = 0x11; + private WebView v2exWebView; + + private class V2exWebViewClientHandler extends Handler { + + V2exWebViewClientHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(@NonNull Message msg) { + super.handleMessage(msg); + try { + String clearAdvertisementJs = AdvertisementFilterUtil.clearAdvertisementDivJs(getContext()); + if (v2exWebView != null) { + v2exWebView.loadUrl(clearAdvertisementJs); + } + }catch (Exception ignored) {} + } + + } + + private class V2exWebViewClient extends WebViewClient { + V2exWebViewClient(Context context) { + v2exWebViewClientHandler = new V2exWebViewClientHandler(context.getMainLooper()); + v2exWebViewClientMsg = new Message(); + } + @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { return super.shouldInterceptRequest(view, url); @@ -156,11 +193,16 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); + v2exWebView = view; + v2exWebViewClientMsg.what = advertisementFilterMsg; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); + if (v2exWebViewClientHandler != null && v2exWebViewClientMsg != null) { + v2exWebViewClientHandler.sendMessage(v2exWebViewClientMsg); + } // download the imgs in mImgs list downloadImgs(); if (onHtmlRenderListener != null) { @@ -183,9 +225,11 @@ private void downloadImgs() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { // TODO: 2018/12/23 click load again - String localPath = "file:///android_asset/html/image_holder_failed.png"; - HtmlView.this.loadUrl("javascript:reloadImg(" + "'" + url + "'" + "," + "'" + localPath + "'" + ");"); - Voast.debug("load image failed"); + HtmlView.this.post(() -> { + String localPath = "file:///android_asset/html/image_holder_failed.png"; + HtmlView.this.loadUrl("javascript:reloadImg(" + "'" + url + "'" + "," + "'" + localPath + "'" + ");"); + Voast.debug("load image failed"); + }); return false; } diff --git a/app/src/main/java/me/ghui/v2er/module/topic/TopicActivity.java b/app/src/main/java/me/ghui/v2er/module/topic/TopicActivity.java index 01bb777d..feac277c 100644 --- a/app/src/main/java/me/ghui/v2er/module/topic/TopicActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/topic/TopicActivity.java @@ -6,6 +6,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.browser.customtabs.CustomTabsClient; import com.google.android.material.bottomsheet.BottomSheetDialog; @@ -18,6 +20,7 @@ import android.text.Html; import android.text.TextWatcher; import android.transition.Transition; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -207,124 +210,137 @@ protected void parseExtras(Intent intent) { } @Override - protected void configToolBar(BaseToolBar toolBar) { - super.configToolBar(toolBar); - Utils.setPaddingForStatusBar(toolBar); - mToolbar.inflateMenu(R.menu.topic_info_toolbar_menu); - Menu menu = mToolbar.getMenu(); + public int attachOptionsMenuRes() { + return R.menu.topic_info_toolbar_menu; + } + + + @Override + public void configOptionsMenu(Menu menu) { + super.configOptionsMenu(menu); mLoveMenuItem = menu.findItem(R.id.action_star); mThxMenuItem = menu.findItem(R.id.action_thx); mAppendItem = menu.findItem(R.id.action_append); mFadeItem = menu.findItem(R.id.action_fade); mStickyItem = menu.findItem(R.id.action_sticky); mReportMenuItem = menu.findItem(R.id.action_report); - MenuItem replyMenuItem = menu.findItem(R.id.action_reply); - mIsHideReplyBtn = Pref.readBool(R.string.pref_key_hide_reply_btn); - replyMenuItem.setVisible(mIsHideReplyBtn); - MenuItem scanOrderMenuItem = menu.findItem(R.id.action_scan_order); + scanOrderMenuItem = menu.findItem(R.id.action_scan_order); scanOrderMenuItem.setTitle(mIsScanInOrder ? "顺序浏览" : "逆序浏览"); - mToolbar.setOnMenuItemClickListener(item -> { - if (mTopicInfo == null) { - if (item.getItemId() == R.id.action_open_in_browser) { - String topicLink = Utils.generateTopicLinkById(mTopicId); - Utils.openInBrowser(topicLink, this); + } + + private MenuItem scanOrderMenuItem; + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (mTopicInfo == null) { + if (item.getItemId() == R.id.action_open_in_browser) { + String topicLink = Utils.generateTopicLinkById(mTopicId); + Utils.openInBrowser(topicLink, this); + } else { + toast("请等到加载完成"); + } + return true; + } + TopicInfo.HeaderInfo headerInfo = mTopicInfo.getHeaderInfo(); + + switch (item.getItemId()) { + case R.id.action_star: + if (headerInfo.hadStared()) { + mPresenter.unStarTopic(mTopicId, mTopicInfo.getOnce()); } else { - toast("请等到加载完成"); + mPresenter.starTopic(mTopicId, mTopicInfo.getOnce()); } - return true; - } - TopicInfo.HeaderInfo headerInfo = mTopicInfo.getHeaderInfo(); - - switch (item.getItemId()) { - case R.id.action_star: - if (headerInfo.hadStared()) { - mPresenter.unStarTopic(mTopicId, mTopicInfo.getOnce()); - } else { - mPresenter.starTopic(mTopicId, mTopicInfo.getOnce()); - } - break; - case R.id.action_append: - AppendTopicActivity.open(mTopicId, TopicActivity.this); - break; - case R.id.action_thx: - if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; - if (mTopicInfo.getHeaderInfo().isSelf()) { - toast("自己不能感谢自己"); - return false; - } - if (!headerInfo.canSendThanks()) { - toast("感谢发送失败,可能因为您刚注册不久"); - return true; - } - if (!headerInfo.hadThanked()) { - mPresenter.thxCreator(mTopicId, getOnce()); - } else { - toast(R.string.already_thx_cannot_return); - return true; - } - break; - case R.id.action_block: - if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; - new ConfirmDialog.Builder(getActivity()) - .msg("确定忽略此主题吗?") - .positiveText(R.string.ok, dialog -> mPresenter.ignoreTopic(mTopicId, mTopicInfo.getOnce())) - .negativeText(R.string.cancel) - .build().show(); - break; - case R.id.action_report: - if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; - new ConfirmDialog.Builder(getActivity()) - .msg("确定要举报这个主题吗?") - .positiveText(R.string.ok, dialog -> mPresenter.reportTopic()) - .negativeText(R.string.cancel) - .build().show(); - break; - case R.id.action_sticky: - if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; - new ConfirmDialog.Builder(getActivity()) - .msg("你确认要将此主题置顶 10 分钟?该操作价格为 200 铜币。") - .positiveText(R.string.ok, dialog -> mPresenter.stickyTopic()) - .negativeText(R.string.cancel) - .build().show(); - break; - case R.id.action_fade: - if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; - new ConfirmDialog.Builder(getActivity()) - .msg("你确认要将此主题下沉 1 天?") - .positiveText(R.string.ok, dialog -> mPresenter.fadeTopic()) - .negativeText(R.string.cancel) - .build().show(); - break; - case R.id.action_share: - ShareManager.ShareData shareData = new ShareManager.ShareData.Builder(headerInfo.getTitle()) - .content(Vtml.fromHtml(mTopicInfo.getContentInfo().getFormattedHtml()).toString()) - .link(UriUtils.topicLink(mTopicId)) - .img(headerInfo.getAvatar()) - .build(); - ShareManager shareManager = new ShareManager(shareData, this); - shareManager.showShareDialog(); - break; - case R.id.action_open_in_browser: + break; + case R.id.action_append: + AppendTopicActivity.open(mTopicId, TopicActivity.this); + break; + case R.id.action_thx: + if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; + if (mTopicInfo.getHeaderInfo().isSelf()) { + toast("自己不能感谢自己"); + return false; + } + if (!headerInfo.canSendThanks()) { + toast("感谢发送失败,可能因为您刚注册不久"); + return true; + } + if (!headerInfo.hadThanked()) { + mPresenter.thxCreator(mTopicId, getOnce()); + } else { + toast(R.string.already_thx_cannot_return); + return true; + } + break; + case R.id.action_block: + if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; + new ConfirmDialog.Builder(getActivity()) + .msg("确定忽略此主题吗?") + .positiveText(R.string.ok, dialog -> mPresenter.ignoreTopic(mTopicId, mTopicInfo.getOnce())) + .negativeText(R.string.cancel) + .build().show(); + break; + case R.id.action_report: + if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; + new ConfirmDialog.Builder(getActivity()) + .msg("确定要举报这个主题吗?") + .positiveText(R.string.ok, dialog -> mPresenter.reportTopic()) + .negativeText(R.string.cancel) + .build().show(); + break; + case R.id.action_sticky: + if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; + new ConfirmDialog.Builder(getActivity()) + .msg("你确认要将此主题置顶 10 分钟?该操作价格为 200 铜币。") + .positiveText(R.string.ok, dialog -> mPresenter.stickyTopic()) + .negativeText(R.string.cancel) + .build().show(); + break; + case R.id.action_fade: + if (UserUtils.notLoginAndProcessToLogin(false, this)) return false; + new ConfirmDialog.Builder(getActivity()) + .msg("你确认要将此主题下沉 1 天?") + .positiveText(R.string.ok, dialog -> mPresenter.fadeTopic()) + .negativeText(R.string.cancel) + .build().show(); + break; + case R.id.action_share: + ShareManager.ShareData shareData = new ShareManager.ShareData.Builder(headerInfo.getTitle()) + .content(Vtml.fromHtml(mTopicInfo.getContentInfo().getFormattedHtml()).toString()) + .link(UriUtils.topicLink(mTopicId)) + .img(headerInfo.getAvatar()) + .build(); + ShareManager shareManager = new ShareManager(shareData, this); + shareManager.showShareDialog(); + break; + case R.id.action_open_in_browser: // Utils.copyToClipboard(this, mTopicInfo.getTopicLink()); // toast("链接已拷贝成功"); - Utils.openInBrowser(mTopicInfo.getTopicLink(), this); - break; - case R.id.action_reply: - animateEditInnerWrapper(true); - break; - case R.id.action_scan_order: - // reload - mIsScanInOrder = !mIsScanInOrder; - scanOrderMenuItem.setTitle(mIsScanInOrder ? "顺序浏览" : "逆序浏览"); - Pref.saveBool(R.string.pref_key_is_scan_in_reverse, !mIsScanInOrder); - mLoadMoreRecyclerView.setLoadOrder(mIsScanInOrder); - // 重新加载 - loadFromStart(); - showLoading(); - break; - } - return true; - }); + Utils.openInBrowser(mTopicInfo.getTopicLink(), this); + break; + case R.id.action_reply: + animateEditInnerWrapper(true); + break; + case R.id.action_scan_order: + // reload + mIsScanInOrder = !mIsScanInOrder; + scanOrderMenuItem.setTitle(mIsScanInOrder ? "顺序浏览" : "逆序浏览"); + Pref.saveBool(R.string.pref_key_is_scan_in_reverse, !mIsScanInOrder); + mLoadMoreRecyclerView.setLoadOrder(mIsScanInOrder); + // 重新加载 + loadFromStart(); + showLoading(); + break; + } + return true; + } + + @Override + protected void configToolBar(BaseToolBar toolBar) { + super.configToolBar(toolBar); + Log.d(this.getClass().getSimpleName(), "configToolBar"); + if (mToolbar != null) { + mToolbar.displayHomeAsUpButton(this); + } } @@ -384,11 +400,11 @@ protected void reloadMode(int mode) { @Override protected void init() { AndroidBug5497Workaround.assistActivity(this); - Utils.setPaddingForNavbar(mReplyLayout); setEnterSharedElementCallback(mCallback); setFirstLoadingDelay(300); shareElementAnimation(); // mReplyFabBtn.setVisibility(!mIsLogin || mIsHideReplyBtn ? View.GONE : VISIBLE); + mIsHideReplyBtn = Pref.readBool(R.string.pref_key_hide_reply_btn); if (!mIsLogin || mIsHideReplyBtn) { mReplyFabBtn.hide(); } else mReplyFabBtn.show(); @@ -580,6 +596,7 @@ public String getOnce() { @Override public void fillView(TopicInfo topicInfo, int page) { + Log.d(this.getClass().getSimpleName(), "fillView"); mTopicInfo = topicInfo; if (mNeedWaitForTransitionEnd) return; if (topicInfo == null) { @@ -610,13 +627,13 @@ public void fillView(TopicInfo topicInfo, int page) { onRenderCompleted(); } TopicInfo.HeaderInfo headerInfo = mTopicInfo.getHeaderInfo(); - updateStarStatus(headerInfo.hadStared(), false); - updateThxCreatorStatus(headerInfo.hadThanked(), false); - updateReportMenuItem(mTopicInfo.hasReportPermission(), mTopicInfo.hasReported()); - boolean isSelf = mTopicInfo.getHeaderInfo().isSelf(); - mAppendItem.setVisible(isSelf && mTopicInfo.getHeaderInfo().canAppend()); - mFadeItem.setVisible(isSelf && mTopicInfo.canfade()); - mStickyItem.setVisible(isSelf && mTopicInfo.canSticky()); + updateStarStatus(headerInfo.hadStared(), false); + updateThxCreatorStatus(headerInfo.hadThanked(), false); + updateReportMenuItem(mTopicInfo.hasReportPermission(), mTopicInfo.hasReported()); + boolean isSelf = mTopicInfo.getHeaderInfo().isSelf(); + mAppendItem.setVisible(isSelf && mTopicInfo.getHeaderInfo().canAppend()); + mFadeItem.setVisible(isSelf && mTopicInfo.canfade()); + mStickyItem.setVisible(isSelf && mTopicInfo.canSticky()); if (!mIsHideReplyBtn && mIsLogin) { // mReplyFabBtn.setVisibility(VISIBLE); mReplyFabBtn.show(); @@ -775,7 +792,7 @@ public void onAnimationCancel(Animator animation) { private void updateStarStatus(boolean isStared, boolean needUpdateData) { mLoveMenuItem.setIcon(isStared ? - R.drawable.ic_star_selected : R.drawable.ic_star_normal); + R.drawable.ic_bookmarked : R.drawable.ic_bookmark); mLoveMenuItem.getIcon().setTint(Theme.getColor(R.attr.icon_tint_color, this)); if (needUpdateData) { mTopicInfo.getHeaderInfo().updateStarStatus(isStared); @@ -913,7 +930,6 @@ public void onKeyboardShown() { @Override public void onKeyboardHidden() { L.d("onKeyboardHidden"); - Utils.setPaddingForNavbar(mReplyLayout); } @Override diff --git a/app/src/main/java/me/ghui/v2er/module/topic/TopicReplyItemDelegate.java b/app/src/main/java/me/ghui/v2er/module/topic/TopicReplyItemDelegate.java index e10c6f8b..f7bc92d4 100644 --- a/app/src/main/java/me/ghui/v2er/module/topic/TopicReplyItemDelegate.java +++ b/app/src/main/java/me/ghui/v2er/module/topic/TopicReplyItemDelegate.java @@ -1,12 +1,21 @@ package me.ghui.v2er.module.topic; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.graphics.Color; import androidx.annotation.Nullable; + +import android.text.Selection; +import android.text.Spannable; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; import android.util.TypedValue; +import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import me.ghui.v2er.util.Check; import me.ghui.v2er.R; @@ -72,6 +81,20 @@ public void convert(ViewHolder holder, TopicInfo.Item item, int position) { img.setImageResource(replyInfo.hadThanked() ? R.drawable.love_checked_icon : R.drawable.love_normal_icon); holder.setText(R.id.time_tv, replyInfo.getTime()); TextView contentView = holder.getView(R.id.content_tv); + Context context = holder.getConvertView().getContext(); + if (context != null) { + ClipboardManager clipboardManager = (ClipboardManager) context + .getSystemService(Context.CLIPBOARD_SERVICE); + TextView finalContentView = contentView; + contentView.setOnLongClickListener(v -> { + if (clipboardManager != null) { + ClipData clip = ClipData.newPlainText(context.getString(R.string.app_name), finalContentView.getText()); + clipboardManager.setPrimaryClip(clip); + Toast.makeText(context, R.string.copy_text_success, Toast.LENGTH_LONG).show(); + } + return true; + }); + } contentView.setTextSize(TypedValue.COMPLEX_UNIT_PX, FontSizeUtil.getContentSize()); if (Check.notEmpty(replyInfo.getReplyContent())) { contentView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/me/ghui/v2er/module/user/UserHomeActivity.java b/app/src/main/java/me/ghui/v2er/module/user/UserHomeActivity.java index 06ff6663..3461ce31 100644 --- a/app/src/main/java/me/ghui/v2er/module/user/UserHomeActivity.java +++ b/app/src/main/java/me/ghui/v2er/module/user/UserHomeActivity.java @@ -2,6 +2,7 @@ import android.app.SharedElementCallback; import android.content.Context; +import android.content.Entity; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.Drawable; @@ -22,6 +23,9 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -118,6 +122,35 @@ public void onMapSharedElements(List names, Map sharedElem private int mAppBarMaxOffset; private LinearLayoutManager mLayoutManager; + public static void open(String userName, Context context, String avatar, View... sourceViews) { + Navigator navigator = Navigator.from(context) + .to(UserHomeActivity.class) + .putExtra(UserHomeActivity.USER_NAME_KEY, userName) + .putExtra(UserHomeActivity.USER_AVATAR_KEY, avatar); + List sourceViewList = new ArrayList<>(sourceViews.length); + Collections.addAll(sourceViewList, sourceViews); + if (sourceViews.length > 0) { + Iterator sourceViewIterator = sourceViewList.iterator(); + while (sourceViewIterator.hasNext()) { + View sourceView = sourceViewIterator.next(); + if (sourceView instanceof ImageView) { + ImageView imgView = (ImageView) sourceView; + if (ViewUtils.isSameImgRes(imgView, R.drawable.avatar_placeholder_drawable) || imgView.getDrawable() == null) { + sourceViewIterator.remove(); + } + } + navigator.putExtra(KEY(sourceView.getTransitionName()+"_key"), + sourceView.getTransitionName()); + } + } + if (sourceViewList.size() > 0) { + View[] shareViews = new View[sourceViewList.size()]; + sourceViewList.toArray(shareViews); + navigator.shareElement(shareViews); + } + navigator.start(); + } + public static void open(String userName, Context context, View sourceView, String avatar) { if (sourceView != null && sourceView instanceof ImageView) { ImageView imgview = (ImageView) sourceView; @@ -141,9 +174,16 @@ protected int attachLayoutRes() { @Override protected BaseToolBar attachToolbar() { + displayStatusBarArea(false); return null; } + @Override + protected void configToolBar(BaseToolBar toolBar) { + super.configToolBar(toolBar); + mToolbar.displayHomeAsUpButton(this); + } + @Override protected void parseExtras(Intent intent) { mUserName = intent.getStringExtra(USER_NAME_KEY); @@ -181,7 +221,6 @@ public void finishAfterTransition() { protected void init() { getWindow().setStatusBarColor(Color.TRANSPARENT); mAvatarImg.setTransitionName(mTransitionName); - Utils.setPaddingForStatusBar(mToolbar); setEnterSharedElementCallback(mCallback); mToolbar.setOnDoubleTapListener(this); mToolbar.setNavigationOnClickListener(view -> onBackPressed()); diff --git a/app/src/main/java/me/ghui/v2er/network/APIService.java b/app/src/main/java/me/ghui/v2er/network/APIService.java index 0f380f5f..56fe5257 100644 --- a/app/src/main/java/me/ghui/v2er/network/APIService.java +++ b/app/src/main/java/me/ghui/v2er/network/APIService.java @@ -1,11 +1,14 @@ package me.ghui.v2er.network; +import android.util.Log; + import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.IOException; import java.net.URISyntaxException; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -18,6 +21,7 @@ import me.ghui.v2er.BuildConfig; import me.ghui.v2er.util.Check; import me.ghui.v2er.util.L; +import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -37,7 +41,10 @@ */ public class APIService { - public static final String WAP_USER_AGENT = "Mozilla/5.0 (Linux; Android 9.0; V2er Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36"; + + public static final String WAP_Android_USER_AGENT = "Mozilla/5.0 (Linux; Android 9.0; V2er Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36"; + + public static final String WAP_IOS_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"; public static final String WEB_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4; V2er) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"; public static final String UA_KEY = "user-agent"; @@ -116,10 +123,36 @@ public Response intercept(Chain chain) throws IOException { String ua = request.header(UA_KEY); if (Check.isEmpty(ua)) { request = request.newBuilder() - .addHeader("user-agent", WAP_USER_AGENT) + .addHeader("user-agent", WAP_Android_USER_AGENT) + .build(); + } + if (request.url().toString().endsWith("png")) { + request = request.newBuilder() + .removeHeader("user-agent") + .addHeader("user-agent", WAP_Android_USER_AGENT) .build(); } try { + if (request.url().host().startsWith(".")) { + try { + HttpUrl.Builder httpUrlBuilder = request.url().newBuilder() + .host(Constants.WWW_HOST_NAME) + .setEncodedPathSegment(0, "t"); + List encodedPathSegments = request.url().encodedPathSegments(); + for (int i = 0; i < request.url().encodedPathSegments().size(); i++) { + if (i < encodedPathSegments.size() - 1) { + httpUrlBuilder.setEncodedPathSegment(i + 1, encodedPathSegments.get(i)); + } else { + httpUrlBuilder.addEncodedPathSegment(encodedPathSegments.get(i)); + } + } + request = request.newBuilder() + .url(httpUrlBuilder.build()) + .build(); + }catch (Exception e) { + e.printStackTrace(); + } + } return chain.proceed(request); } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/me/ghui/v2er/network/APIs.java b/app/src/main/java/me/ghui/v2er/network/APIs.java index 8259319e..1beb4205 100644 --- a/app/src/main/java/me/ghui/v2er/network/APIs.java +++ b/app/src/main/java/me/ghui/v2er/network/APIs.java @@ -96,10 +96,12 @@ public interface APIs { @Html @GET("/my/following") + @Headers("user-agent: " + APIService.WAP_IOS_USER_AGENT) Observable specialCareInfo(@Query("p") int page); @Html @GET("/my/topics") + @Headers("user-agent: " + APIService.WAP_IOS_USER_AGENT) Observable topicStarInfo(@Query("p") int page); @Html diff --git a/app/src/main/java/me/ghui/v2er/network/Constants.java b/app/src/main/java/me/ghui/v2er/network/Constants.java index 35332e9b..6b716630 100644 --- a/app/src/main/java/me/ghui/v2er/network/Constants.java +++ b/app/src/main/java/me/ghui/v2er/network/Constants.java @@ -9,5 +9,6 @@ public interface Constants { String HTTP_SCHEME = "http:"; String BASE_URL = HTTPS_SCHEME + "//www.v2ex.com"; String HOST_NAME = "v2ex.com"; + String WWW_HOST_NAME = "www.v2ex.com"; String PACKAGE_NAME = "me.ghui.v2ex"; } diff --git a/app/src/main/java/me/ghui/v2er/network/GeneralConsumer.java b/app/src/main/java/me/ghui/v2er/network/GeneralConsumer.java index 2a5202ed..416566e2 100644 --- a/app/src/main/java/me/ghui/v2er/network/GeneralConsumer.java +++ b/app/src/main/java/me/ghui/v2er/network/GeneralConsumer.java @@ -55,53 +55,55 @@ public void onNext(T t) { 3. no premission to open the page */ GeneralError generalError = new GeneralError(ResultCode.NETWORK_ERROR, "Unknown Error"); - String response = t.getResponse(); - generalError.setResponse(response); - Observable.just(response) - .compose(RxUtils.io_main()) - .map(s -> { - BaseInfo resultInfo = APIService.fruit().fromHtml(s, LoginParam.class); - if (resultInfo == null) return null; - if (!resultInfo.isValid()) { - resultInfo = APIService.fruit().fromHtml(s, NewsInfo.class); - } - if (!resultInfo.isValid()) { - resultInfo = APIService.fruit().fromHtml(s, TwoStepLoginInfo.class); - } - // 31/07/2017 More tries... - return resultInfo; - }) - .subscribe(new BaseConsumer() { - @Override - public void onConsume(BaseInfo resultInfo) { - if (resultInfo == null || !resultInfo.isValid()) { - onGeneralError(generalError); - return; + if (t.getResponse() != null) { + String response = t.getResponse(); + generalError.setResponse(response); + Observable.just(response) + .compose(RxUtils.io_main()) + .map(s -> { + BaseInfo resultInfo = APIService.fruit().fromHtml(s, LoginParam.class); + if (resultInfo == null) return null; + if (!resultInfo.isValid()) { + resultInfo = APIService.fruit().fromHtml(s, NewsInfo.class); + } + if (!resultInfo.isValid()) { + resultInfo = APIService.fruit().fromHtml(s, TwoStepLoginInfo.class); } - if (resultInfo instanceof LoginParam) { - if (UserUtils.isLogin()) { - generalError.setErrorCode(ResultCode.LOGIN_EXPIRED); - generalError.setMessage("登录已过期,请重新登录"); - UserUtils.clearLogin(); - } else { - generalError.setErrorCode(ResultCode.LOGIN_NEEDED); - generalError.setMessage("需要您先去登录"); + // 31/07/2017 More tries... + return resultInfo; + }) + .subscribe(new BaseConsumer() { + @Override + public void onConsume(BaseInfo resultInfo) { + if (resultInfo == null || !resultInfo.isValid()) { + onGeneralError(generalError); + return; + } + if (resultInfo instanceof LoginParam) { + if (UserUtils.isLogin()) { + generalError.setErrorCode(ResultCode.LOGIN_EXPIRED); + generalError.setMessage("登录已过期,请重新登录"); + UserUtils.clearLogin(); + } else { + generalError.setErrorCode(ResultCode.LOGIN_NEEDED); + generalError.setMessage("需要您先去登录"); + } + } else if (resultInfo instanceof NewsInfo) { + generalError.setErrorCode(ResultCode.REDIRECT_TO_HOME); + generalError.setMessage("Redirecting to home"); + } else if (resultInfo instanceof TwoStepLoginInfo) { + generalError.setErrorCode(ResultCode.LOGIN_TWO_STEP); + generalError.setMessage("Two Step Login"); } - } else if (resultInfo instanceof NewsInfo) { - generalError.setErrorCode(ResultCode.REDIRECT_TO_HOME); - generalError.setMessage("Redirecting to home"); - } else if (resultInfo instanceof TwoStepLoginInfo) { - generalError.setErrorCode(ResultCode.LOGIN_TWO_STEP); - generalError.setMessage("Two Step Login"); + onGeneralError(generalError); } - onGeneralError(generalError); - } - @Override - public void onError(Throwable e) { - onGeneralError(e); - } - }); + @Override + public void onError(Throwable e) { + onGeneralError(e); + } + }); + } } } diff --git a/app/src/main/java/me/ghui/v2er/network/bean/ExplorePageInfo.java b/app/src/main/java/me/ghui/v2er/network/bean/ExplorePageInfo.java new file mode 100644 index 00000000..c951b332 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/network/bean/ExplorePageInfo.java @@ -0,0 +1,56 @@ +package me.ghui.v2er.network.bean; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class ExplorePageInfo extends BaseInfo { + + private final List items = new ArrayList<>(); + + public List getItems() { + if (items.size() > 0) { + items.clear(); + } + if (dailyHotInfo != null) { + items.add(dailyHotInfoTitle); + items.addAll(dailyHotInfo); + } + if (nodesNavInfo != null) { + items.add(nodesNavInfoTitle); + items.addAll(nodesNavInfo); + } + return items; + } + + public DailyHotInfo dailyHotInfo; + public String dailyHotInfoTitle; + + public void setDailyHotInfo(DailyHotInfo dailyHotInfo, String title) { + this.dailyHotInfo = dailyHotInfo; + this.dailyHotInfoTitle = title; + } + + public void setDailyHotInfoTitle(String dailyHotInfoTitle) { + this.dailyHotInfoTitle = dailyHotInfoTitle; + } + + public NodesNavInfo nodesNavInfo; + private String nodesNavInfoTitle; + + public void setNodesNavInfo(NodesNavInfo nodesNavInfo, String title) { + this.nodesNavInfo = nodesNavInfo; + this.nodesNavInfoTitle = title; + } + + public void setNodesNavInfoTitle(String nodesNavInfoTitle) { + this.nodesNavInfoTitle = nodesNavInfoTitle; + } + + @Override + public boolean isValid() { + return this.dailyHotInfo.isValid() || this.nodesNavInfo.isValid(); + } + + +} diff --git a/app/src/main/java/me/ghui/v2er/network/bean/ExplorePageInfoWrapper.java b/app/src/main/java/me/ghui/v2er/network/bean/ExplorePageInfoWrapper.java new file mode 100644 index 00000000..af549ea3 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/network/bean/ExplorePageInfoWrapper.java @@ -0,0 +1,23 @@ +package me.ghui.v2er.network.bean; + +import java.io.Serializable; + +public class ExplorePageInfoWrapper extends BaseInfo implements Serializable { + + public ExplorePageInfo explorePageInfo; + + + private ExplorePageInfoWrapper(ExplorePageInfo explorePageInfo) { + this.explorePageInfo = explorePageInfo; + } + + public static ExplorePageInfoWrapper wrapper(ExplorePageInfo explorePageInfo) { + return new ExplorePageInfoWrapper(explorePageInfo); + } + + @Override + public boolean isValid() { + if (explorePageInfo == null) return false; + return explorePageInfo.isValid(); + } +} diff --git a/app/src/main/java/me/ghui/v2er/network/bean/NodesNavInfoWrapper.java b/app/src/main/java/me/ghui/v2er/network/bean/NodesNavInfoWrapper.java deleted file mode 100644 index 5a84f8d8..00000000 --- a/app/src/main/java/me/ghui/v2er/network/bean/NodesNavInfoWrapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.ghui.v2er.network.bean; - -import java.io.Serializable; - -public class NodesNavInfoWrapper extends BaseInfo implements Serializable { - - public NodesNavInfo nodesNavInfo; - - private NodesNavInfoWrapper(NodesNavInfo nodesNavInfo) { - this.nodesNavInfo = nodesNavInfo; - } - - public static NodesNavInfoWrapper wrapper(NodesNavInfo nodesNavInfo) { - return new NodesNavInfoWrapper(nodesNavInfo); - } - - @Override - public boolean isValid() { - if (nodesNavInfo == null) return false; - return nodesNavInfo.isValid(); - } -} diff --git a/app/src/main/java/me/ghui/v2er/util/AdvertisementFilterUtil.java b/app/src/main/java/me/ghui/v2er/util/AdvertisementFilterUtil.java new file mode 100644 index 00000000..b33fb384 --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/util/AdvertisementFilterUtil.java @@ -0,0 +1,50 @@ +package me.ghui.v2er.util; + +import android.content.Context; +import android.content.res.Resources; + +import me.ghui.v2er.R; + +public class AdvertisementFilterUtil { + + public static String clearAdvertisementDivJs(Context context){ + StringBuilder js = new StringBuilder("javascript:"); + Resources res = context.getResources(); + String[] adDivs = res.getStringArray(R.array.advertisementBlockDiv); + for(int i=0;i 0) { + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + if (view instanceof TextView) { + if (mTitleView != null) { + mSubTitleView = (TextView) view; + break; + } + mTitleView = (TextView) view; + } + } + } + } + + /** + * 必须有主标题标题剧中才会生效 + * @param isCenter + */ + public void setTileCenter(boolean isCenter) { + if (isCenter) { + initTitleViewAndSubTitleView(); + if (mTitleView != null) { + mTitleView.setGravity(Gravity.CENTER); + Toolbar.LayoutParams layoutParams = (Toolbar.LayoutParams) mTitleView.getLayoutParams(); + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.CENTER; + mTitleView.setLayoutParams(layoutParams); + if (mSubTitleView != null) { + mSubTitleView.setGravity(Gravity.CENTER); + Toolbar.LayoutParams subTitleLayoutParams = (Toolbar.LayoutParams) mSubTitleView.getLayoutParams(); + subTitleLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + subTitleLayoutParams.gravity = Gravity.CENTER; + mSubTitleView.setLayoutParams(subTitleLayoutParams); + } + float marginStart = 0; + float marginEnd = 0; + if (getNavigationIcon() != null) { + marginEnd = ScaleUtils.dp(GENERATED_ITEM_MARGIN + GENERATED_ITEM_PADDING * 3); + } else if (getMenu().size() > 0) { + marginStart = ScaleUtils.dp(MIN_CELL_SIZE - GENERATED_ITEM_PADDING * 3); + } + setTitleMargin((int) marginStart, 0, (int) marginEnd, 0); + } + } + } + + private View mViewTitleView; + + public void setViewTileCenter(View viewTitleView) { + this.mViewTitleView = viewTitleView; + if (mViewTitleView != null) { + Toolbar.LayoutParams layoutParams = (Toolbar.LayoutParams) mViewTitleView.getLayoutParams(); + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.CENTER; + float marginStart = 0; + float marginEnd = 0; + if (getNavigationIcon() != null) { + marginEnd = ScaleUtils.dp(GENERATED_ITEM_MARGIN + GENERATED_ITEM_PADDING * 3); + } else if (getMenu().size() > 0) { + marginStart = ScaleUtils.dp(MIN_CELL_SIZE - GENERATED_ITEM_PADDING * 3); + } + layoutParams.setMargins((int) marginStart, 0, (int) marginEnd, 0); + mViewTitleView.setLayoutParams(layoutParams); + } + } + + @Override + public void setElevation(float elevation) { + super.setElevation(elevation); + ViewGroup barParentViewGroup = (ViewGroup) this.getParent(); + if (barParentViewGroup != null) { + if (barParentViewGroup instanceof AppBarLayout) { + barParentViewGroup.setElevation(elevation); + } + } + } + + /** + * 设置默认返回按键, 并默认可用 + */ + public void displayHomeAsUpButton(AppCompatActivity activity) { + activity.setSupportActionBar(this); + if (activity.getSupportActionBar() != null) { + activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + activity.getSupportActionBar().setHomeButtonEnabled(true); + } + if (getNavigationIcon() != null) { + this.getNavigationIcon().setTint(Theme.getColor(R.attr.icon_tint_color, getContext())); + } + setNavigationOnClickListener(v -> activity.onBackPressed()); } + /** + * 设置右边菜单 + * 如果调用 displayHomeAsUpButton 会导致此方法失效,可以在onCreateOptionsMenu方法中创建菜单,或 + * 在BaseActivity的attachOptionsMenuRes 方法返回菜单资源 + * @param resId + */ @Override public void inflateMenu(int resId) { super.inflateMenu(resId); diff --git a/app/src/main/java/me/ghui/v2er/widget/SectionItemView.java b/app/src/main/java/me/ghui/v2er/widget/SectionItemView.java new file mode 100644 index 00000000..5169391e --- /dev/null +++ b/app/src/main/java/me/ghui/v2er/widget/SectionItemView.java @@ -0,0 +1,103 @@ +package me.ghui.v2er.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; + +import me.ghui.v2er.R; +import me.ghui.v2er.util.ScaleUtils; +import me.ghui.v2er.util.Utils; + +public class SectionItemView extends RelativeLayout implements View.OnClickListener { + private Drawable icon; + private String title; + private boolean showDivider; + private TextView sectionTitle; + private ImageView sectionIcon; + private DividerView sectionDivider; + + private OnSectionClickListener onSectionClickListener; + + public void setOnSectionClickListener(OnSectionClickListener onSectionClickListener) { + this.onSectionClickListener = onSectionClickListener; + } + + public interface OnSectionClickListener { + void onClick(View v); + } + + public SectionItemView(@NonNull Context context) { + super(context); + initView(context, null); + } + + public SectionItemView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initView(context, attrs); + } + + public SectionItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context, attrs); + } + + public SectionItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initView(context, attrs); + } + + private void initAttrs(Context context, AttributeSet attrs) { + if (attrs != null) { + TypedArray styledAttrs = getContext().getTheme().obtainStyledAttributes(attrs, + R.styleable.SectionItemView, 0, 0); + icon = styledAttrs.getDrawable(R.styleable.SectionItemView_icon); + title = styledAttrs.getString(R.styleable.SectionItemView_title); + showDivider = styledAttrs.getBoolean(R.styleable.SectionItemView_show_divider, true); + styledAttrs.recycle(); + } + } + + private ViewGroup rootView; + private RelativeLayout sectionLayout; + + private void initView(Context context, AttributeSet attrs) { + initAttrs(context, attrs); + rootView = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.section_item_view_layout, this, true); + sectionLayout = rootView.findViewById(R.id.section_layout); + sectionTitle = rootView.findViewById(R.id.section_title); + sectionIcon = rootView.findViewById(R.id.section_icon); + sectionDivider = rootView.findViewById(R.id.section_divider); + sectionLayout.setOnClickListener(this); + if (icon != null) { + sectionIcon.setImageDrawable(icon); + } + if (!TextUtils.isEmpty(title)) { + sectionTitle.setText(title); + } + if (!showDivider) { + sectionDivider.setVisibility(View.INVISIBLE); + } + } + + @Override + public void onClick(View v) { + if (onSectionClickListener != null) { + onSectionClickListener.onClick(this); + } + } + +} diff --git a/app/src/main/java/me/ghui/v2er/widget/richtext/RichTextConfig.java b/app/src/main/java/me/ghui/v2er/widget/richtext/RichTextConfig.java index 679d5032..b01884f4 100644 --- a/app/src/main/java/me/ghui/v2er/widget/richtext/RichTextConfig.java +++ b/app/src/main/java/me/ghui/v2er/widget/richtext/RichTextConfig.java @@ -5,10 +5,16 @@ import android.text.SpannableStringBuilder; import android.widget.TextView; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + import me.ghui.v2er.general.Vtml; import me.ghui.v2er.module.imgviewer.ImagesInfo; import me.ghui.v2er.network.APIService; import me.ghui.v2er.util.ScaleUtils; +import me.ghui.v2er.util.Utils; /** @@ -77,13 +83,31 @@ public RichTextConfig supportUrlClick(boolean supportUrlClick) { return this; } + private Document parseCfEmail(String sourceText) { + Document sourceDocument = Jsoup.parseBodyFragment(sourceText); + Elements cfElements = sourceDocument.select("a"); + for (Element cfElement : cfElements) { + String cfEmail = cfElement.attr("data-cfemail"); + String cfHref = cfElement.attr("href"); + System.out.println(cfEmail); + if (!cfEmail.isEmpty()) { + String email = Utils.cfDecodeEmail(cfEmail.replaceAll("\"", "")); + cfElement.text(email); + cfElement.removeAttr("data-cfemail"); + cfElement.attr("href", "mailto:"+email); + } +// System.out.println(cfElement); + } + return sourceDocument; + } + public void into(TextView textView) { if (!noImg && mImageGetter == null) { mImageHolder = new ImageHolder(textView, maxSize, mLoadingDrawable, mLoaderrorDrawable); mImageGetter = new GlideImageGetter(textView, mImageHolder); } if (sourceText == null) sourceText = ""; - SpannableStringBuilder spanned = (SpannableStringBuilder) Html.fromHtml(sourceText, mImageGetter, mTagHandler); + SpannableStringBuilder spanned = (SpannableStringBuilder) Html.fromHtml(parseCfEmail(sourceText).toString(), mImageGetter, mTagHandler); CharSequence content = Vtml.removePadding(spanned); textView.setText(content); ImagesInfo.Images images = APIService.fruit().fromHtml(sourceText, ImagesInfo.Images.class); diff --git a/app/src/main/res/drawable-xxhdpi/ic_send.png b/app/src/main/res/drawable-xxhdpi/ic_send.png deleted file mode 100644 index 5da547f8..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_send.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share.png b/app/src/main/res/drawable-xxhdpi/ic_share.png deleted file mode 100644 index 5ff85810..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_share.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_normal.png b/app/src/main/res/drawable-xxhdpi/ic_star_normal.png deleted file mode 100644 index 3a99755e..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_star_normal.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_selected.png b/app/src/main/res/drawable-xxhdpi/ic_star_selected.png deleted file mode 100644 index d2e62d28..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_star_selected.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_arrow_back_black.xml b/app/src/main/res/drawable/ic_arrow_back_black.xml deleted file mode 100644 index 010ad884..00000000 --- a/app/src/main/res/drawable/ic_arrow_back_black.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_arrow_back_white.xml b/app/src/main/res/drawable/ic_arrow_back_white.xml deleted file mode 100644 index 0d96512f..00000000 --- a/app/src/main/res/drawable/ic_arrow_back_white.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100755 index 00000000..a9850bbe --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml new file mode 100644 index 00000000..6e302829 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bookmarked.xml b/app/src/main/res/drawable/ic_bookmarked.xml new file mode 100644 index 00000000..20b742b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmarked.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorites.xml b/app/src/main/res/drawable/ic_favorites.xml new file mode 100644 index 00000000..2ab4e6a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorites.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index af0694d4..75ab35dd 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -6,12 +6,6 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_paper_plane.xml b/app/src/main/res/drawable/ic_paper_plane.xml new file mode 100644 index 00000000..e5f820d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_paper_plane.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_pencil.xml b/app/src/main/res/drawable/ic_pencil.xml new file mode 100644 index 00000000..5401933f --- /dev/null +++ b/app/src/main/res/drawable/ic_pencil.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sets.xml b/app/src/main/res/drawable/ic_sets.xml new file mode 100644 index 00000000..9be725d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_sets.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 00000000..9eef0785 --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_shortcuts_bookmark.xml b/app/src/main/res/drawable/ic_shortcuts_bookmark.xml new file mode 100644 index 00000000..4b4e2bb4 --- /dev/null +++ b/app/src/main/res/drawable/ic_shortcuts_bookmark.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_shortcuts_star.xml b/app/src/main/res/drawable/ic_shortcuts_star.xml deleted file mode 100644 index 0d67e67f..00000000 --- a/app/src/main/res/drawable/ic_shortcuts_star.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/main_bottom_navigation_selector.xml b/app/src/main/res/drawable/main_bottom_navigation_selector.xml new file mode 100644 index 00000000..f76a1247 --- /dev/null +++ b/app/src/main/res/drawable/main_bottom_navigation_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/act_main.xml b/app/src/main/res/layout-land/act_main.xml new file mode 100644 index 00000000..892385be --- /dev/null +++ b/app/src/main/res/layout-land/act_main.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/act_gallery.xml b/app/src/main/res/layout/act_gallery.xml index d8599798..577921cc 100644 --- a/app/src/main/res/layout/act_gallery.xml +++ b/app/src/main/res/layout/act_gallery.xml @@ -26,14 +26,13 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - + app:contentInsetStart="0dp"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/act_login.xml b/app/src/main/res/layout/act_login.xml index eb94b4c4..1c99d2e7 100644 --- a/app/src/main/res/layout/act_login.xml +++ b/app/src/main/res/layout/act_login.xml @@ -2,12 +2,13 @@ @@ -17,13 +18,13 @@ android:layout_height="wrap_content" android:autofillHints="username" android:inputType="textWebEditText" - android:textColor="?attr/icon_tint_color" + android:textColor="?attr/bodyTextColor" android:textSize="@dimen/largeTextSize" /> @@ -48,7 +49,7 @@ diff --git a/app/src/main/res/layout/act_main.xml b/app/src/main/res/layout/act_main.xml index ee1f137a..408fa2d9 100644 --- a/app/src/main/res/layout/act_main.xml +++ b/app/src/main/res/layout/act_main.xml @@ -1,61 +1,54 @@ - - - - - - - + + - - - + android:gravity="center" + android:scaleType="fitCenter" + android:src="@drawable/v2ex_title_logo" + android:visibility="gone" + app:tint="?attr/icon_tint_color" /> + - - + - + - - - - - - + + - \ No newline at end of file + android:contentDescription="@string/acc_bottom_nav_menu" + app:layout_constraintTop_toBottomOf="@+id/main_container" + app:layout_constraintBottom_toBottomOf="parent" + app:labelVisibilityMode="labeled" + app:itemIconTint="@drawable/main_bottom_navigation_selector" + app:itemTextColor="@drawable/main_bottom_navigation_selector" + app:menu="@menu/bottom_menu_main" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/act_new_topic.xml b/app/src/main/res/layout/act_new_topic.xml index 6bdb282f..4397819e 100644 --- a/app/src/main/res/layout/act_new_topic.xml +++ b/app/src/main/res/layout/act_new_topic.xml @@ -10,8 +10,11 @@ android:id="@+id/create_topic_title_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="标题" - android:textColorHint="?attr/icon_tint_color"> + style="@style/MaterialComponents.TextInputLayout.FilledBox" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + android:hint="标题"> + app:tint="?attr/icon_tint_color" /> + app:tint="?attr/icon_tint_color" /> + diff --git a/app/src/main/res/layout/act_user_page.xml b/app/src/main/res/layout/act_user_page.xml index 33b6de8e..7d74cf4f 100644 --- a/app/src/main/res/layout/act_user_page.xml +++ b/app/src/main/res/layout/act_user_page.xml @@ -57,6 +57,7 @@ android:layout_width="wrap_content" android:gravity="center" android:paddingTop="6dp" + android:transitionName="@string/share_element_username" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/user_img" tools:text="ghui" /> @@ -113,8 +114,9 @@ android:id="@+id/user_info_toobar" style="@style/BaseToolBar" android:background="@android:color/transparent" - app:layout_collapseMode="pin" - app:navigationIcon="@drawable/ic_arrow_back_black" /> + app:titleTextColor="?attr/icon_tint_color" + app:subtitleTextColor="?attr/icon_tint_color" + app:layout_collapseMode="pin" /> diff --git a/app/src/main/res/layout/appbar_wrapper_toolbar.xml b/app/src/main/res/layout/appbar_wrapper_toolbar.xml index e49ad609..9190938a 100644 --- a/app/src/main/res/layout/appbar_wrapper_toolbar.xml +++ b/app/src/main/res/layout/appbar_wrapper_toolbar.xml @@ -1,7 +1,8 @@ diff --git a/app/src/main/res/layout/common_list_item.xml b/app/src/main/res/layout/common_list_item.xml index eb929626..e15dee19 100644 --- a/app/src/main/res/layout/common_list_item.xml +++ b/app/src/main/res/layout/common_list_item.xml @@ -1,6 +1,7 @@ + + - - diff --git a/app/src/main/res/layout/common_recyclerview_layout.xml b/app/src/main/res/layout/common_recyclerview_layout.xml index c29b98be..0010da2f 100644 --- a/app/src/main/res/layout/common_recyclerview_layout.xml +++ b/app/src/main/res/layout/common_recyclerview_layout.xml @@ -3,5 +3,6 @@ android:id="@+id/base_recyclerview" style="@style/BaseRecyclerView" android:clipToPadding="false" - android:paddingTop="16dp" /> + android:paddingTop="8dp" + android:paddingBottom="8dp"/> diff --git a/app/src/main/res/layout/common_title.xml b/app/src/main/res/layout/common_title.xml new file mode 100644 index 00000000..79294985 --- /dev/null +++ b/app/src/main/res/layout/common_title.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/frag_search_layout.xml b/app/src/main/res/layout/frag_search_layout.xml index d7458118..034373c6 100644 --- a/app/src/main/res/layout/frag_search_layout.xml +++ b/app/src/main/res/layout/frag_search_layout.xml @@ -49,6 +49,7 @@ style="@style/SearchInput" android:layout_width="0dp" android:layout_height="match_parent" + android:contentDescription="@string/acc_search_edittext" android:layout_weight="1" android:gravity="center_vertical" android:hint="Powered by sov2ex" @@ -63,7 +64,7 @@ android:layout_height="@dimen/searchbar_size" android:background="?selectableItemBackgroundBorderless" android:clickable="true" - android:contentDescription="clear text" + android:contentDescription="@string/acc_search_clear" android:focusable="true" android:padding="17.5dp" android:src="@drawable/ic_close_black" diff --git a/app/src/main/res/layout/fragment_mine.xml b/app/src/main/res/layout/fragment_mine.xml new file mode 100644 index 00000000..2f117439 --- /dev/null +++ b/app/src/main/res/layout/fragment_mine.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/message_count_layout.xml b/app/src/main/res/layout/message_count_layout.xml new file mode 100644 index 00000000..19c2ef1d --- /dev/null +++ b/app/src/main/res/layout/message_count_layout.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/mine_section_layout.xml b/app/src/main/res/layout/mine_section_layout.xml new file mode 100644 index 00000000..224c3715 --- /dev/null +++ b/app/src/main/res/layout/mine_section_layout.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/mine_user_info_simple_layout.xml b/app/src/main/res/layout/mine_user_info_simple_layout.xml new file mode 100644 index 00000000..e1c45fa7 --- /dev/null +++ b/app/src/main/res/layout/mine_user_info_simple_layout.xml @@ -0,0 +1,81 @@ + + + + + +